Guide

Type safety

Give each name its own type, so a post TNID passed as a user TNID doesn't compile. At runtime, parsing rejects the wrong name.

One type per name

Each name gets its own TNID type:

use tnid::{NameStr, Tnid, TnidName};

struct User;
impl TnidName for User {
    const ID_NAME: NameStr<'static> = NameStr::new_const("user");
}

let user_id = Tnid::<User>::new_v0();

fn delete_user(id: Tnid<User>) { /* ... */ }
delete_user(user_id); // OK; passing a Tnid<Post> here is a compile error
import { Tnid, type TnidType } from "@tnid/core";

const UserId = Tnid("user");
type UserId = TnidType<typeof UserId>;

const userId: UserId = UserId.new_v0();

function deleteUser(id: UserId) { /* ... */ }
deleteUser(userId); // OK; passing a PostId, or a plain string, is a type error

Invalid names such as "User" or "users" fail to compile too.

At runtime

Parsing a string from a request, file or database checks the name:

Tnid::<User>::parse_tnid_string("post.Br2flcNDfF6LYICnT") // returns an error
UserId.parseTnidString("post.Br2flcNDfF6LYICnT") // throws
  • Name unknown in advance (generic logging, admin tools): parse into the library's DynamicTnid type, which accepts any valid TNID and lets you read its name.
  • Languages whose type system can't carry the name: rely on this runtime check wherever TNIDs enter your code.