Typed Named ID

UUID compatible, but strictly improved

  • Typed. Each TNID carries its type name, so code can reject the wrong kind at runtime, and type checkers at compile time.
  • Sortable. Sorting the strings as text sorts the TNIDs by value; TNIDv0 sorts by creation time.
  • Readable. user.Br2flcNDfF6LYICnT names what it identifies. A UUID doesn’t.

TNID is a draft (spec version 0); libraries are pre-1.0.

One 128-bit value, two stringsTNIDv1
TNID
test.x8MRU0xetVa6QZeZR
22 chars
UUID
cab19f49-5dc7-8c1f-9ab9-8261db92a91c
36 chars
TNIDUUID
Says what it identifiesTNID yes — prefix testUUID no
One exact spellingTNID yes — case-sensitiveUUID no — upper or lower hex
URL-safeTNID yesUUID yes
Valid UUIDTNID yes — UUIDv8UUID yes
nameUUID version and variant (fixed)TNID variantdata
01

One value, four representations

Store it as a UUID or integer; show it as a TNID string.

Guide: Storing and showing TNIDs →
TNID string
test.x8MRU0xetVa6QZeZR
UUID form
cab19f49-5dc7-8c1f-9ab9-8261db92a91c
u128 (hex)
0xCAB19F495DC78C1F9AB98261DB92A91C
bytes
CA B1 9F 49 5D C7 8C 1F 9A B9 82 61 DB 92 A9 1C
Guide: Storing and showing TNIDs →
02

Bit layout

One digit per bit, numbered by the part it belongs to.

Implementing: Bit layout →
1111.1111.1111.1111.1111.5555.5555.5555-
5555.5555.5555.5555-
2222.5555.5555.5555-
3344.5555.5555.5555-
5555.5555.5555.5555.5555.5555.5555.5555.5555.5555.5555.5555
scroll →128 bits
#ComponentBitsHolds
1Name20 bits1–4 characters, 5 bits each
2UUID version4 bitsMarks it as UUIDv8
3UUID variant2 bitsMarks it as a standard UUID
4TNID variant2 bits0: TNIDv0, 1: TNIDv1, 2–3: reserved
5Payload100 bitsDefined by the variant
Implementing: Bit layout →
03

Variants

TNIDv0 (variant 0) is time-sortable and, like UUIDv7, reveals its creation time. TNIDv1 is random.

Guide: Choosing a variant →
TNIDv0
Time-sortable
  • Millisecond timestamp plus 57 random bits
  • TNIDs with the same name sort in creation order
  • Recommended default for database keys
TNIDv1
Random
  • 100 random bits
  • No ordering, no embedded time
  • For public TNIDs that shouldn’t reveal creation time
Variants 2, 3Reserved, not yet defined.
Guide: Choosing a variant →
04

The trade-offs

Real, but rarely decisive. For nearly every system, TNIDs are a better default than UUIDs.

Guide: Compared with other IDs →
  • Collision odds Higher than UUIDv4’s, still negligible: a billion TNIDv1s per name have about a 1 in 2.5 trillion collision chance.
  • Permanent names The name is in the value; renaming a type changes all its TNIDs.
  • Case-sensitive by design One spelling per TNID makes the strings sortable. Case-insensitive columns can’t tell them apart; store the UUID form instead.
  • Draft spec The spec is version 0, marked draft.
Guide: Compared with other IDs →
05

Checked by the compiler

Where the language allows, the name is part of the type. A Post TNID won’t compile where a User TNID is expected.

Guide: Type safety →

struct User;
impl TnidName for User {
    const ID_NAME: NameStr<'static> = NameStr::new_const("user");
}
 
let user_id = Tnid::<User>::new_v0();
println!("{}", user_id);  // user.Br2flcNDfF6LYICnT
 
fn delete_user(id: Tnid<User>) { /* … */ }
 
delete_user(user_id);compiles
delete_user(post_id);error[E0308]: mismatched types

const UserId = Tnid("user");
type UserId = TnidType<typeof UserId>;
 
const userId = UserId.new_v0();
console.log(userId);  // user.Br2flcNDfF6LYICnT
 
function deleteUser(id: UserId) { /* … */ }
 
deleteUser(userId);compiles
deleteUser(postId);error TS2345: Type 'TnidValue<"post">' is not assignable to 'TnidValue<"user">'
Guide: Type safety →