UUIDs Land in Luma: Generate, Validate, No Dependencies

UUIDs Land in Luma: Generate, Validate, No Dependencies

February 15, 2026·
Luma Core Team

UUIDs are everywhere. Database primary keys, API request IDs, session tokens, distributed system identifiers, file names that need to be globally unique — they’re one of the most used primitives in modern software. And until today, generating one in Luma meant pulling in an external module or cobbling together random bytes by hand.

Not anymore. Luma now has a built-in uuid namespace with three functions that cover the real-world cases.

What shipped

uuid.v4()

Generates a random UUID v4 — the most common variant. 122 bits of cryptographic randomness, formatted as the standard 8-4-4-4-12 hex string.

id: str = uuid.v4()
print(id)   // e.g. "3f2504e0-4f89-41d3-9a0c-0305e82c3301"

This is the UUID you reach for when you need a unique identifier and don’t care about ordering. Database rows, API keys, correlation IDs — v4 is the workhorse.

uuid.v7()

Generates a time-ordered UUID v7 (RFC 9562). The first 48 bits encode the Unix timestamp in milliseconds. The rest is random.

id: str = uuid.v7()
print(id)   // e.g. "019c61fc-bec8-7c55-8bda-8898f5b91e18"

The killer feature: v7 UUIDs sort chronologically. Generate them at different times and they naturally sort by creation order. This makes them ideal for database primary keys — you get uniqueness and insert-order locality, which means better index performance compared to random v4 UUIDs.

ids: [str] = []
(1..=5).walk(i) -> {
    ids = ids.append(uuid.v7())
}
ids.walk(id) -> {
    print(id)
}
// Output is already in chronological order

uuid.is_valid()

Validates that a string is a properly formatted UUID (any version).

print(uuid.is_valid("550e8400-e29b-41d4-a716-446655440000"))   // true
print(uuid.is_valid("not-a-uuid"))                               // false

Checks the 8-4-4-4-12 format with valid hex characters and dashes at the correct positions. Useful for validating user input, API parameters, or data from external sources before using it as an identifier.

Built-in, not a module

UUIDs are too fundamental to live behind an import. Like random, crypto, and json, the uuid namespace is built into the language. No imports, no dependencies, no setup:

id: str = uuid.v4()

That’s it. One line. The compiler includes the UUID runtime code only when you actually use uuid.* — programs that don’t use UUIDs pay nothing.

v4 vs. v7: when to use which

Scenario Use Why
Database primary keys uuid.v7() Time-ordered, better index performance
Correlation / request IDs uuid.v4() Pure randomness, no time leakage
Distributed event ordering uuid.v7() Natural chronological sort
Opaque tokens uuid.v4() No information encoded
Log trace IDs uuid.v7() Sortable, debuggable by time
Idempotency keys uuid.v4() Just needs uniqueness

Rule of thumb: if ordering matters, use v7. If you just need a unique string, use v4.

What you can build

Unique file names

extension: str = ".png"
filename: str = uuid.v4() + extension
print("Saving as: ${filename}")
// e.g. "a1b2c3d4-e5f6-4789-abcd-ef0123456789.png"

No collisions. No counters. No timestamps in the name. Just guaranteed uniqueness.

Request tracing

fn handle_request(path: str) -> str {
    request_id: str = uuid.v7()
    print("[${request_id}] Processing: ${path}")
    // ... do work ...
    print("[${request_id}] Done")
    return request_id
}

v7 here so log lines sort naturally by time across distributed services.

Validate before use

fn find_user(id: str) -> str {
    if !uuid.is_valid(id) {
        return "Invalid ID format"
    }
    // ... look up user ...
    return "Found user: ${id}"
}

print(find_user("019c61fc-bec8-7c55-8bda-8898f5b91e18"))
print(find_user("garbage"))

Catch bad input at the boundary before it reaches your database layer.

Generate a batch of IDs

ids: [str] = []
(1..=10).walk(i) -> {
    ids = ids.append(uuid.v4())
}
ids.walk(id) -> {
    print(id)
}

Under the hood

Both uuid.v4() and uuid.v7() use crypto/rand for their random bits — the same OS-level entropy source as crypto.rand(). These aren’t pseudo-random UUIDs. They’re backed by real cryptographic randomness, which means collision probability is astronomically low (2^-122 for v4).

v7 additionally encodes time.Now().UnixMilli() in the first 48 bits, then sets the version nibble to 7 and the variant bits per RFC 9562.

uuid.is_valid() is a pure format check — it validates the 36-character structure (8-4-4-4-12 hex with dashes) but doesn’t check version or variant bits. Any correctly formatted UUID passes, regardless of version.

Design decisions

Three functions, not a UUID type. UUIDs are strings. They’re compared as strings, stored as strings, transmitted as strings. Wrapping them in a custom type adds ceremony without adding safety. str is the right representation for a language that values simplicity.

Built-in, not a module. We debated this. Modules are great for domain-specific functionality like CSV parsing or JWT tokens. But UUIDs are infrastructure-level — they show up in almost every non-trivial program. Making developers import a module for something this fundamental would feel like friction, not organization.

Cryptographic randomness for both versions. Some UUID libraries use math/rand for v4. We don’t. The performance difference is negligible for UUID generation, and using crypto/rand means every UUID from Luma is safe to use as a security token if needed. No footgun.

Validation is format-only. uuid.is_valid() doesn’t reject based on version or variant. A v1 UUID from another system, a v4 from Luma, a v7 with any variant — all pass if the format is correct. This is the most useful behavior for input validation at API boundaries.

What’s next

The UUID namespace covers the two most important versions and a validator. If demand emerges for v5 (name-based SHA-1) or for extracting the timestamp from a v7, those are natural additions. But v4 and v7 handle the overwhelming majority of real-world UUID needs.

Generate, validate, move on. That’s the Luma way.

Last updated on