Tnid

Struct Tnid 

Source
pub struct Tnid<Name: TnidName> { /* private fields */ }
Expand description

A type-safe TNID parameterized by name.

The type parameter uses the TnidName trait to enforce compile-time checking of names. Tnid<User> and Tnid<Post> are distinct types that cannot be mixed.

All validation happens at construction time, so any Tnid<Name> instance is guaranteed to be valid. If you need to work with potentially invalid 128-bit values, use UUIDLike for inspection without validation.

Implementations§

Source§

impl<Name: TnidName> Tnid<Name>

Source

pub fn name(&self) -> &'static str

Returns the name associated with this TNID type.

The name comes from the TnidName implementation for this type and is used in the TNID string representation (<name>.<data>).

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

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

let id = Tnid::<User>::new_v0();
assert_eq!(id.name(), "user");
Source

pub fn name_hex(&self) -> String

Returns the hex representation of the name field (20 bits as 5 hex characters).

The ASCII representation of a name (like “test”) is different from the hex representation of those bits when viewing a TNID in hex format. This method shows what the name looks like as hex, which is how it appears in TNID hex strings.

This is useful for understanding what the name portion looks like in the hex representation without needing a specific TNID instance.

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

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

// Check what "test" looks like in hex (any TNID instance works)
let id = Tnid::<Test>::new_v1();
assert_eq!(id.name_hex(), "cab19");
Source

pub fn as_u128(&self) -> u128

Returns the raw 128-bit UUIDv8-compatible representation of this TNID.

This returns the complete bit representation including the name, UUID version/variant bits, TNID variant, and all data bits.

§Endianness

The UUID specification dictates that UUIDs are stored in big-endian byte order. When storing or transmitting this u128 value as bytes, you may need to convert to big-endian format using methods like u128::to_be_bytes() since u128 uses the platform’s native endianness.

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

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

let id = Tnid::<User>::new_v0();
let as_u128 = id.as_u128();

// Convert to big-endian bytes for storage/transmission
let bytes = as_u128.to_be_bytes();
Source

pub fn new_time_ordered() -> Self

Generates a new time-ordered TNID (alias for Self::new_v0).

This variant is sortable by creation time, similar to UUIDv7. TNIDs created earlier will sort before those created later in all representations (u128, UUID hex, TNID string).

Use this when you need time-based sorting, similar to choosing UUIDv7 over UUIDv4.

Source

pub fn new_v0() -> Self

Available on crate feature time only.

Generates a new v0 TNID.

This variant is time-ordered with millisecond precision, similar to UUIDv7. TNIDs created earlier will sort before those created later in all representations (u128, UUID hex, and TNID string). The remaining bits are filled with random data.

Use this when you need time-based sorting and want IDs to be roughly chronological, similar to choosing UUIDv7 over UUIDv4.

Source

pub fn new_high_entropy() -> Self

Available on crate feature rand only.

Generates a new TNID with maximum randomness (alias for Self::new_v1).

This variant maximizes entropy with 100 bits of random data, similar to UUIDv4 but with slightly less entropy due to the 20-bit name field. This is almost certainly sufficient for most use cases.

Use this when you don’t need time-based sorting and want maximum randomness, similar to choosing UUIDv4 over UUIDv7.

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

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

let id = Tnid::<User>::new_high_entropy();
Source

pub fn new_v1() -> Self

Available on crate feature rand only.

Generates a new v1 TNID.

This variant maximizes entropy with 100 bits of random data, similar to UUIDv4. This is almost certainly sufficient for most use cases.

Use this when you don’t need time-based sorting and want maximum randomness, similar to choosing UUIDv4 over UUIDv7.

Source

pub fn new_v1_with_random(random_bits: u128) -> Self

Generates a new high-entropy TNID (v1) from explicit random bits.

This creates a v1 TNID without requiring the rand feature dependency, allowing you to provide your own randomness source. This is useful in environments where the rand library may not be suitable (embedded systems, WASM, or when you need a custom random source).

§Parameters
  • random_bits: Random bits for the TNID. Only 100 bits are used, but accepting a u128 makes it easier to provide randomness.
§Examples
use tnid::{Tnid, TnidName, NameStr};

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

// Provide your own randomness
let random_bits = 0x0123456789ABCDEF0123456789ABCDEF;

let id = Tnid::<User>::new_v1_with_random(random_bits);
Source

pub fn new_v0_with_time(time: OffsetDateTime) -> Self

Available on crate features rand and time only.

Generates a new time-ordered TNID (v0) with a specific timestamp.

This creates the same time-sortable TNID as Self::new_v0, but allows you to provide a specific timestamp instead of using the current time. The timestamp is converted to milliseconds since the Unix epoch and encoded into the TNID.

TNIDs created with earlier timestamps will sort before those with later timestamps in all representations (u128, UUID hex, and TNID string).

§Examples
use tnid::{Tnid, TnidName, NameStr};
use time::OffsetDateTime;

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

let timestamp = OffsetDateTime::now_utc();
let id = Tnid::<User>::new_v0_with_time(timestamp);

Demonstrating time-based sorting:

use tnid::{Tnid, TnidName, NameStr};
use time::{OffsetDateTime, Duration};

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

let now = OffsetDateTime::now_utc();
let earlier = now - Duration::hours(1);
let later = now + Duration::hours(1);

let id1 = Tnid::<User>::new_v0_with_time(earlier);
let id2 = Tnid::<User>::new_v0_with_time(now);
let id3 = Tnid::<User>::new_v0_with_time(later);

// Earlier times sort before later times
assert!(id1.as_u128() < id2.as_u128());
assert!(id2.as_u128() < id3.as_u128());
Source

pub fn new_v0_with_parts(epoch_millis: u64, random: u64) -> Self

Generates a new time-ordered TNID (v0) from explicit components.

This creates a v0 TNID without requiring the time or rand feature dependencies, allowing you to provide your own timestamp and randomness sources. This is useful in environments where those libraries may not be suitable (embedded systems, WASM, or when you need custom time/random sources).

§Parameters
  • epoch_millis: Milliseconds since the Unix epoch (January 1, 1970 UTC)
  • random: Random bits for the TNID (57 bits will be used)
§Examples
use tnid::{Tnid, TnidName, NameStr};

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

// Provide your own timestamp and randomness
let timestamp_ms = 1750118400000;
let random_bits = 42;

let id = Tnid::<User>::new_v0_with_parts(timestamp_ms, random_bits);
Source

pub fn as_tnid_string(&self) -> String

Returns the TNID string representation.

This representation has several advantages over the UUID hex format:

  • Unambiguous: Unlike UUID hex strings which are case-insensitive, TNID strings are case-sensitive with exactly one valid representation
  • Sortable: For v0 TNIDs, the string representation maintains time-ordering
  • Human-readable name: The name prefix makes it easy to identify the ID type

The format is <name>.<encoded-data>, where the data is encoded using the TNID Data Encoding that preserves these sortability and uniqueness properties.

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

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

let id = Tnid::<User>::new_v0();
let tnid_string = id.as_tnid_string();

// Format: <name>.<encoded-data>
// Example: "user.Br2flcNDfF6LYICnT"
assert!(tnid_string.starts_with("user."));
Source

pub fn variant(&self) -> TnidVariant

Returns the TNID variant.

§Examples
use tnid::{Tnid, TnidName, NameStr, TnidVariant};

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

let id_v0 = Tnid::<User>::new_v0();
assert_eq!(id_v0.variant(), TnidVariant::V0);

let id_v1 = Tnid::<User>::new_v1();
assert_eq!(id_v1.variant(), TnidVariant::V1);
Source

pub fn to_uuid_string_cased(&self, uppercase: bool) -> String

Converts the TNID to UUID hex string format.

This is useful for UUID compatibility and interoperability with systems that expect standard UUID format, or any other case where you need the common UUID hex representation.

§Parameters
  • uppercase: If true, uses uppercase hex digits (A-F). If false, uses lowercase (a-f).
§Examples
use tnid::{Tnid, TnidName, NameStr};

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

let id = Tnid::<User>::new_v1();

let uuid_lower = id.to_uuid_string_cased(false);
// "cab1952a-f09d-86d9-928e-96ea03dc6af3"

let uuid_upper = id.to_uuid_string_cased(true);
// "CAB1952A-F09D-86D9-928E-96EA03DC6AF3"
Source

pub fn parse_uuid_string(uuid_string: &str) -> Option<Self>

Parses a TNID from UUID hex string format.

This is the inverse of Self::to_uuid_string_cased.

The parser accepts both uppercase and lowercase hex digits (A-F or a-f).

Returns None if:

  • The string is not valid UUID format
  • The UUID is not a valid TNID (wrong version/variant bits or name mismatch)

For inspecting why a UUID might not be a valid TNID, see UUIDLike.

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

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

// Create a TNID and convert to UUID string
let original = Tnid::<User>::new_v1();
let uuid_string = original.to_uuid_string_cased(false);

// Parse it back
let parsed = Tnid::<User>::parse_uuid_string(&uuid_string);
assert!(parsed.is_some());
assert_eq!(parsed.unwrap().as_u128(), original.as_u128());

// Also accepts uppercase
let uuid_upper = original.to_uuid_string_cased(true);
let parsed_upper = Tnid::<User>::parse_uuid_string(&uuid_upper);
assert!(parsed_upper.is_some());

// Invalid: not a valid UUID format
assert!(Tnid::<User>::parse_uuid_string("not-a-uuid").is_none());
Source

pub fn parse_tnid_string(tnid_string: &str) -> Option<Self>

Parses a TNID from its string representation.

This is the inverse of Self::as_tnid_string. See that method for details on the TNID string format.

Returns None if the string is invalid. Validation includes:

  • Correct format (<name>.<encoded-data>)
  • Name matches the expected name for this TNID type
  • Valid TNID Data Encoding
  • Correct UUIDv8 version and variant bits

If you need to inspect non-compliant IDs or understand why parsing failed, consider using UUIDLike which provides lower-level access.

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

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

// Successful parsing
let parsed = Tnid::<User>::parse_tnid_string("user.Br2flcNDfF6LYICnT");
assert!(parsed.is_some());

// Failed parsing - wrong name
assert!(Tnid::<User>::parse_tnid_string("post.Br2flcNDfF6LYICnT").is_none());

// Failed parsing - invalid format
assert!(Tnid::<User>::parse_tnid_string("not-a-tnid").is_none());
Source

pub fn from_u128(id: u128) -> Option<Self>

Creates a TNID from a raw 128-bit value.

This is the inverse of Self::as_u128 and is useful for loading TNIDs from databases that store UUIDs as u128/binary, interoperating with UUID-based systems, or deserializing.

Returns None if the value is not a valid TNID. Validation includes:

  • Correct UUIDv8 version and variant bits
  • Name encoding matches the expected name for this TNID type
§Endianness

When loading from bytes, you’ll almost certainly want to parse a [u8; 16] to a u128 using big-endian byte order with u128::from_be_bytes(), as per the UUID specification.

Source

pub fn encrypt_v0_to_v1( &self, key: impl Into<EncryptionKey>, ) -> Result<Self, ()>

Available on crate feature encryption only.

Encrypts a V0 TNID to a V1 TNID, hiding timestamp information.

V0 TNIDs contain a timestamp (like UUIDv7), which may leak information when exposed publicly. This method encrypts the data bits to produce a valid V1 TNID that hides the timestamp while remaining decryptable with Self::decrypt_v1_to_v0.

See the encryption module for more details on why and how this works.

§Parameters
  • secret: 128-bit (16 bytes) encryption key
§Returns
  • Ok(encrypted) for V0 input (encrypts and converts to V1)
  • Ok(self) for V1 input (already encrypted, returns unchanged)
  • Err(()) for V2/V3 input (unsupported variants)
§Example
use tnid::{Tnid, TnidName, NameStr, TNIDVariant};

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

let secret = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];

let original = Tnid::<User>::new_v0();
let encrypted = original.encrypt_v0_to_v1(secret).unwrap();
assert_eq!(encrypted.variant(), TnidVariant::V1);

let decrypted = encrypted.decrypt_v1_to_v0(secret).unwrap();
assert_eq!(decrypted.as_u128(), original.as_u128());
Source

pub fn decrypt_v1_to_v0( &self, key: impl Into<EncryptionKey>, ) -> Result<Self, ()>

Available on crate feature encryption only.

Decrypts a V1 TNID back to a V0 TNID, recovering timestamp information.

This is the inverse of Self::encrypt_v0_to_v1. See the encryption module for more details.

§Parameters
  • secret: 128-bit (16 bytes) encryption key (must match the key used for encryption)
§Returns
  • Ok(decrypted) for V1 input (decrypts and converts to V0)
  • Ok(self) for V0 input (already decrypted, returns unchanged)
  • Err(()) for V2/V3 input (unsupported variants)
§Example
use tnid::{Tnid, TnidName, NameStr, TNIDVariant};

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

let secret = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];

let original = Tnid::<User>::new_v0();
let encrypted = original.encrypt_v0_to_v1(secret).unwrap();

let decrypted = encrypted.decrypt_v1_to_v0(secret).unwrap();
assert_eq!(decrypted.variant(), TnidVariant::V0);
assert_eq!(decrypted.as_u128(), original.as_u128());

Trait Implementations§

Source§

impl<Name: TnidName> Clone for Tnid<Name>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<Name: TnidName> Debug for Tnid<Name>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<Name: TnidName> Display for Tnid<Name>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<Name: TnidName> From<Tnid<Name>> for Uuid

Available on crate feature uuid only.

Convert a Tnid to a uuid::Uuid.

This conversion is infallible because all Tnids are valid UUIDv8 identifiers by design.

Source§

fn from(tnid: Tnid<Name>) -> Self

Converts to this type from the input type.
Source§

impl<Name: Hash + TnidName> Hash for Tnid<Name>

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl<Name: Ord + TnidName> Ord for Tnid<Name>

Source§

fn cmp(&self, other: &Tnid<Name>) -> Ordering

This method returns an Ordering between self and other. Read more
1.21.0 · Source§

fn max(self, other: Self) -> Self
where Self: Sized,

Compares and returns the maximum of two values. Read more
1.21.0 · Source§

fn min(self, other: Self) -> Self
where Self: Sized,

Compares and returns the minimum of two values. Read more
1.50.0 · Source§

fn clamp(self, min: Self, max: Self) -> Self
where Self: Sized,

Restrict a value to a certain interval. Read more
Source§

impl<Name: PartialEq + TnidName> PartialEq for Tnid<Name>

Source§

fn eq(&self, other: &Tnid<Name>) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl<Name: PartialOrd + TnidName> PartialOrd for Tnid<Name>

Source§

fn partial_cmp(&self, other: &Tnid<Name>) -> Option<Ordering>

This method returns an ordering between self and other values if one exists. Read more
1.0.0 · Source§

fn lt(&self, other: &Rhs) -> bool

Tests less than (for self and other) and is used by the < operator. Read more
1.0.0 · Source§

fn le(&self, other: &Rhs) -> bool

Tests less than or equal to (for self and other) and is used by the <= operator. Read more
1.0.0 · Source§

fn gt(&self, other: &Rhs) -> bool

Tests greater than (for self and other) and is used by the > operator. Read more
1.0.0 · Source§

fn ge(&self, other: &Rhs) -> bool

Tests greater than or equal to (for self and other) and is used by the >= operator. Read more
Source§

impl<Name: TnidName> TryFrom<Uuid> for Tnid<Name>

Available on crate feature uuid only.

Attempt to convert a uuid::Uuid to a Tnid.

This conversion is fallible because not all UUIDs are valid Tnids:

  • The UUID must be UUIDv8 (version 8)
  • The UUID must have the correct variant bits (RFC4122)
  • The name encoding in the top 20 bits must match the expected Tnid name type
Source§

type Error = TnidFromUuidError

The type returned in the event of a conversion error.
Source§

fn try_from(uuid: Uuid) -> Result<Self, Self::Error>

Performs the conversion.
Source§

impl<Name: TnidName> Copy for Tnid<Name>

Source§

impl<Name: Eq + TnidName> Eq for Tnid<Name>

Source§

impl<Name: TnidName> StructuralPartialEq for Tnid<Name>

Auto Trait Implementations§

§

impl<Name> Freeze for Tnid<Name>

§

impl<Name> RefUnwindSafe for Tnid<Name>
where Name: RefUnwindSafe,

§

impl<Name> Send for Tnid<Name>
where Name: Send,

§

impl<Name> Sync for Tnid<Name>
where Name: Sync,

§

impl<Name> Unpin for Tnid<Name>
where Name: Unpin,

§

impl<Name> UnwindSafe for Tnid<Name>
where Name: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V