hanki

uuid

stdlib/extra/uuid.hk: RFC 9562 UUIDs, v4 for identity and v7 for order.

A Uuid is sixteen bytes, opaque so the version and variant bits cannot be broken by arithmetic on them. It renders and parses in the canonical 8-4-4-4-12 lowercase form alone, which is the form every wire protocol and database column agrees on.

Two versions, answering different questions:

new_v4! 122 random bits. The identity is unrelated to when it was made, which is what it is for: nothing leaks and nothing collides. new_v7! a 48-bit Unix-millisecond prefix and a random tail. Sorts by creation time as a string, as bytes, and as a database key, so a primary key remains cache-friendly in place of scattering the index the way v4 does.

Parsing and rendering are pure; only generation is effectful. new_v4! charges [random] and new_v7! [random, time], and a program's capability surface therefore says which one it reached for.

Under --deterministic the random draw comes from the seeded stream, and a run that generates UUIDs replays them: the same seed gives the same identifiers, which is what makes a test that stores one and asserts on it reproducible and no snapshot that changes every run. new_v7! also reads the gate's virtual clock, and its prefix replays too.

Uuid

opaque Uuid
  raw: bytes
end

A UUID: sixteen bytes with the version and variant bits set per RFC 9562.

Opaque because the bit layout is a contract with every other implementation in the world. Construct one with new_v4!/new_v7! or Uuid.parse, read it back with to_string or to_bytes.

impl Uuid

nil

def nil() -> Uuid

The nil UUID, all sixteen bytes zero: RFC 9562's explicit "no value".

It is the one UUID with no version - version answers 0u8 - so it is distinguishable from any generated one and never merely unlikely.

Uuid.nil().to_string() => "00000000-0000-0000-0000-000000000000"
Uuid.nil().version     => 0u8

to_bytes

def to_bytes(self) -> bytes

The sixteen bytes, big-endian, as they travel on a wire or into a BINARY(16) column.

Uuid.nil().to_bytes().length => 16

version

prop version(self) -> u8

The version nibble: 4u8 for new_v4!, 7u8 for new_v7!, 0u8 for the nil UUID.

Uuid.nil().version => 0u8

impl Display<Uuid>

to_string

def to_string(self) -> string

The canonical form: 32 lowercase hex digits in 8-4-4-4-12 groups.

Lowercase because RFC 9562 requires generated output to be lowercase, even though parsing accepts either case.

Uuid.nil().to_string() => "00000000-0000-0000-0000-000000000000"

impl FromString<Uuid>

parse

def parse(s: string) -> Result<Uuid, ParseError>

Read a UUID in canonical 8-4-4-4-12 form. Case-insensitive, since the same value arrives upper-cased from plenty of systems, but the dashes are required and so is the length: a bare 32-digit string is a different format, and accepting it here would make this parser the one that decides what a UUID looks like.

Uuid.parse("00000000-0000-0000-0000-000000000000").map(|u| u.version).unwrap_or(9u8) => 0u8
Uuid.parse("not-a-uuid").map(|u| u.version).unwrap_or(9u8)                           => 9u8

impl Eq<Uuid>

eq?

def eq?(self, other: Self) -> bool

Two UUIDs are equal when all sixteen bytes are.

(Uuid.nil() == Uuid.nil()) => true

impl Ord<Uuid>

cmp

def cmp(self, other: Self) -> Ordering

Byte order, which for a v7 UUID is creation order: the timestamp is the big-endian prefix, and sorting the identifiers sorts the rows.

Uuid.nil().cmp(Uuid.nil()) => Equal

impl Hash<Uuid>

hash

prop hash(self) -> u64

Hashes the bytes, which lets a Uuid work as a Map key.

(Uuid.nil().hash == Uuid.nil().hash) => true

new_v4!

def new_v4!() -> Uuid [random]

A version 4 UUID: 122 random bits, with the six version and variant bits fixed. Nothing about it says when or where it was made.

Charges [random] alone: no clock is read, which is the difference from new_v7! in both capability and behaviour. @no-doctest: draws random bytes; no fixed value to assert

new_v7!

def new_v7!() -> Uuid [random, time]

A version 7 UUID: a 48-bit Unix-millisecond prefix and 74 random bits, with the same six bits fixed. Two UUIDs made in different milliseconds sort in the order they were made, as text and as bytes alike.

Charges [random, time]: the clock read is what produces the ordering, and a signature that hides it would be lying about the dependency. @no-doctest: reads the clock and draws random bytes; no fixed value to assert

_stamp

def _stamp(b: bytes, version: u8) -> bytes

Set the version nibble (high half of byte 6) and the variant bits (high two bits of byte 8 to 10), leaving the other 122 bits alone. Every UUID this module produces goes through here, which asserts the RFC 9562 layout in one place and never at each constructor.

_be48

def _be48(ms: i64) -> bytes

The low 48 bits of ms, big-endian, which is the v7 prefix. Big-endian is what makes the byte order the time order.

_zeros

def _zeros() -> bytes

Sixteen zero bytes.

_refused?

def _refused?(s: string) -> bool

Did parse reject s? Result has no emptiness predicate, and the refusals are therefore asserted through one match and never six.

_dashed?

def _dashed?(s: string) -> bool

Are the four dashes where the canonical form puts them? The caller has already checked the length.

_ones

def _ones() -> bytes