Skip to content

gershnik/modern-uuid

Repository files navigation

modern-uuid

Language Standard License Tests

A modern, no-dependencies, portable C++ library for manipulating UUIDs, ULIDs, NanoIDs and Cuid2s.

Features

  • UUID: Implements the newer RFC 9562 (which supersedes the older RFC 4122). Supports generation of UUID variants 1, 3, 5, 6 and 7.

  • ULID: Implements the canonical spec.

  • NanoID: Since there is no formal spec, this library implements an external textual format identical to the JavaScript library and a generation algorithm fully equivalent to it. It also supports custom alphabets and sizes with the same semantics. The implementation is done from first principles - it is not a port of the JavaScript library.

    • In particular, NanoID objects are internally stored and manipulated in a packed binary format rather than strings, resulting in much better memory efficiency.
  • Self-contained with no dependencies beyond the C++ standard library.

  • Works on Mac, Linux, Windows, BSD, Wasm, Illumos and even Haiku OS. Might even work on some embedded systems given a suitable compiler and standard library support.

  • Requires C++20 but does not require a very recent compiler (GCC is supported from version 10 and Clang from version 13).

  • Most operations (with the obvious exceptions of XXID generation and iostream I/O) are constexpr and can be done at compile time. Notably, this enables:

    • Natural syntax for compile-time XXID literals.
    • Using XXIDs as template parameters and in other compile-time contexts.
  • Supports std::format (if available) for formatting and parsing in addition to iostreams.

  • Does not rely on C++ exceptions and can be used with C++ exceptions disabled.

  • Uses only "safe" constructs in the public interface (no raw pointers and such).

  • Properly handles fork with no exec on Unix systems. XXIDs generated by the child process will not collide with the parent's.

See also differences from other UUID libraries.

Usage

A quick intro to the library is given below. For more details, see:

UUID

#include <modern-uuid/uuid.h>

using namespace muuid;

//this is a compile-time UUID literal
constexpr uuid u1("e53d37db-e4e0-484f-996f-3ab1d4701abc");

//if you want to, you can use uuid as a template parameter
template<uuid U> class some_class {...};
some_class<uuid("bc961bfb-b006-42f4-93ae-206f02658810")> some_object;

//you can generate all non-proprietary versions of UUID from RFC 9562:
uuid u_v1 = uuid::generate_time_based();
uuid u_v3 = uuid::generate_md5(uuid::namespaces::dns, "www.widgets.com");
uuid u_v4 = uuid::generate_random();
uuid u_v5 = uuid::generate_sha1(uuid::namespaces::dns, "www.widgets.com");
uuid u_v6 = uuid::generate_reordered_time_based();
uuid u_v7 = uuid::generate_unix_time_based();

//for non-literal strings, you can parse uuids from strings using uuid::from_chars
//the argument to from_chars can be anything convertible to std::span<char>
//the call is constexpr
std::string some_uuid_str = "7D444840-9DC0-11D1-B245-5FFDCE74FAD2";
std::optional<uuid> maybe_uuid = uuid::from_chars(some_uuid_str);
if (maybe_uuid) {
    uuid parsed = *maybe_uuid;
}

//uuid objects can be compared in every possible way
assert(u_v1 > uuid());
assert(u_v1 != u_v3);
std::strong_ordering res = (u_v6 <=> u_v7);
//etc.

//uuid objects can be hashed
std::unordered_map<uuid, transaction> transaction_map;

//they can be formatted. u and l stand for uppercase and lowercase

std::string str = std::format("{}", u1);
assert(str == "e53d37db-e4e0-484f-996f-3ab1d4701abc");

str = std::format("{:u}", u1);
assert(str == "E53D37DB-E4E0-484F-996F-3AB1D4701ABC");

str = std::format("{:l}", u1);
assert(str == "e53d37db-e4e0-484f-996f-3ab1d4701abc");

//uuids can be read/written from/to iostreams

//when reading, case doesn't matter
std::istringstream istr("bc961bfb-b006-42f4-93ae-206f02658810");
uuid uuidr;
istr >> uuidr;
assert(uuidr == uuid("bc961bfb-b006-42f4-93ae-206f02658810"));

std::ostringstream ostr;
ostr << uuid("bc961bfb-b006-42f4-93ae-206f02658810");
assert(ostr.str() == "bc961bfb-b006-42f4-93ae-206f02658810");
ostr.str("");

//writing respects the std::ios_base::uppercase stream flag
ostr << std::uppercase << uuid("7d444840-9dc0-11d1-b245-5ffdce74fad2");
assert(ostr.str() == "7D444840-9DC0-11D1-B245-5FFDCE74FAD2");

ULID

#include <modern-uuid/ulid.h> //note the 'l'

using namespace muuid;

//this is a compile-time ULID literal
constexpr ulid u1("01BX5ZZKBKACTAV9WEVGEMMVRY");

//if you want to, you can use ulid as a template parameter
template<ulid U1> class some_class {...};
some_class<ulid("01BX5ZZKBKACTAV9WEVGEMMVRY")> some_object;

ulid u2 = ulid::generate();

//for non-literal strings, you can parse ulids from strings using ulid::from_chars
//the argument to from_chars can be anything convertible to std::span<char>
//the call is constexpr
std::string some_ulid_str = "01bx5zzkbkactav9wevgemmvry";
std::optional<ulid> maybe_ulid = ulid::from_chars(some_ulid_str);
if (maybe_ulid) {
    ulid parsed = *maybe_ulid;
}

//ulid objects can be compared in every possible way
assert(u2 > ulid());
assert(u2 != u1);
std::strong_ordering res = (u2 <=> u1);
//etc.

//ulid objects can be hashed
std::unordered_map<ulid, transaction> transaction_map;

//they can be formatted. u and l stand for uppercase and lowercase
std::string str = std::format("{}", u1);
assert(str == "01bx5zzkbkactav9wevgemmvry");

str = std::format("{:u}", u1);
assert(str == "01BX5ZZKBKACTAV9WEVGEMMVRY");

str = std::format("{:l}", u1);
assert(str == "01bx5zzkbkactav9wevgemmvry");

//ulids can be read/written from/to iostreams

//when reading, case doesn't matter
std::istringstream istr("01bx5zzkbkactav9wevgemmvry");
ulid ur;
istr >> ur;
assert(ur == ulid("01bx5zzkbkactav9wevgemmvry"));

std::ostringstream ostr;
ostr << ulid("01bx5zzkbkactav9wevgemmvry");
assert(ostr.str() == "01bx5zzkbkactav9wevgemmvry");
ostr.str("");

//writing respects the std::ios_base::uppercase stream flag
ostr << std::uppercase << ulid("01bx5zzkbkactav9wevgemmvry");
assert(ostr.str() == "01BX5ZZKBKACTAV9WEVGEMMVRY");

NanoID

#include <modern-uuid/nanoid.h>

using namespace muuid;

//this is a compile-time NanoID literal
constexpr nanoid n1("V1StGXR8_Z5jdHi6B-myT");

//if you want to, you can use nanoid as a template parameter
template<nanoid N> class some_class {...};
some_class<nanoid("V1StGXR8_Z5jdHi6B-myT")> some_object;

//generate a NanoID:
nanoid ng = nanoid::generate();

//for non-literal strings, you can parse nanoids from strings using nanoid::from_chars
//the argument to from_chars can be anything convertible to std::span<char, any extent>
//the call is constexpr
std::string some_nanoid_str = "Uakgb_J5m9g-0JDMbcJqL";
std::optional<nanoid> maybe_nanoid = nanoid::from_chars(some_nanoid_str);
if (maybe_nanoid) {
    nanoid parsed = *maybe_nanoid;
}

//nanoid objects can be compared in every possible way
assert(ng > nanoid());
assert(ng != n1);
std::strong_ordering res = (ng <=> n1);
//etc.

//nanoid objects can be hashed
std::unordered_map<nanoid, transaction> transaction_map;

//they can be formatted. 

std::string str = std::format("{}", n1);
assert(str == "V1StGXR8_Z5jdHi6B-myT");

//nanoids can be read/written from/to iostreams

std::istringstream istr("V1StGXR8_Z5jdHi6B-myT");
nanoid nr;
istr >> nr;
assert(nr == nanoid("V1StGXR8_Z5jdHi6B-myT"));

std::ostringstream ostr;
ostr << nanoid("V1StGXR8_Z5jdHi6B-myT");
assert(ostr.str() == "V1StGXR8_Z5jdHi6B-myT");

//You can define different nanoid types that use custom alphabets and sizes
MUUID_DECLARE_NANOID_ALPHABET(my_alphabet, "1234567890abcdef");

using my_id = basic_nanoid<my_alphabet, 10>;

//Note that the size of such an ID might be different from the default nanoid
static_assert(sizeof(my_id) == 5);

constexpr my_id mid1("4f90d13a42");
my_id mid2 = my_id::generate();
//etc.
//all other nanoid operations are the same (with different string/buffer sizes)
//as for the default nanoid

Cuid2

#include <modern-uuid/cuid2.h>

using namespace muuid;

//this is a compile-time Cuid2 literal
constexpr cuid2 c1("kgiupd0rsg67b97553xdrg2f");

//if you want to, you can use cuid2 as a template parameter
template<cuid2 C> class some_class {...};
some_class<cuid2("kgiupd0rsg67b97553xdrg2f")> some_object;

//generate a Cuid2:
cuid2 cg = cuid2::generate();

//for non-literal strings, you can parse cuid2s from strings using cuid2::from_chars
//the argument to from_chars can be anything convertible to std::span<char, any extent>
//the call is constexpr
std::string some_cuid2_str = "a96dz76vcu55q6aorin51oqo";
std::optional<cuid2> maybe_cuid2 = cuid2::from_chars(some_cuid2_str);
if (maybe_cuid2) {
    cuid2 parsed = *maybe_cuid2;
}

//cuid2 objects can be compared in every possible way
assert(cg > cuid2());
assert(cg != c1);
std::strong_ordering res = (cg <=> c1);
//etc.

//cuid2 objects can be hashed
std::unordered_map<cuid2, transaction> transaction_map;

//they can be formatted. u and l stand for uppercase and lowercase
std::string str = std::format("{}", c1);
assert(str == "kgiupd0rsg67b97553xdrg2f");

str = std::format("{:u}", c1);
assert(str == "KGIUPD0RSG67B97553XDRG2F");

str = std::format("{:l}", c1);
assert(str == "kgiupd0rsg67b97553xdrg2f");

//cuid2s can be read/written from/to iostreams

//when reading, case doesn't matter
std::istringstream istr("kgiupd0rsg67b97553xdrg2f");
cuid2 cr;
istr >> cr;
assert(cr == cuid2("kgiupd0rsg67b97553xdrg2f"));

std::ostringstream ostr;
ostr << cuid2("kgiupd0rsg67b97553xdrg2f");
assert(ostr.str() == "kgiupd0rsg67b97553xdrg2f");
ostr.str("");

//writing respects the std::ios_base::uppercase stream flag
ostr << std::uppercase << cuid2("kgiupd0rsg67b97553xdrg2f");
assert(ostr.str() == "KGIUPD0RSG67B97553XDRG2F");

Building/Integrating

The quickest CMake method is given below. For more details and other methods, see Integration Guide.

include(FetchContent)
FetchContent_Declare(modern-uuid
    GIT_REPOSITORY git@github.com:gershnik/modern-uuid.git
    GIT_TAG        <desired tag like v1.2 or a sha>
    GIT_SHALLOW    TRUE
)
FetchContent_MakeAvailable(modern-uuid)
...
target_link_libraries(mytarget
PRIVATE
  modern-uuid::modern-uuid
)

About

A modern, no-dependencies, portable C++ library for manipulating UUIDs, ULIDs, NanoIDs and Cuid2s.

Topics

Resources

License

Stars

Watchers

Forks

Contributors