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>
impl<Name: TnidName> Tnid<Name>
Sourcepub fn name(&self) -> &'static str
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");Sourcepub fn name_hex(&self) -> String
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");Sourcepub fn as_u128(&self) -> u128
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();Sourcepub fn new_time_ordered() -> Self
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.
Sourcepub fn new_v0() -> Self
Available on crate feature time only.
pub fn new_v0() -> Self
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.
Sourcepub fn new_high_entropy() -> Self
Available on crate feature rand only.
pub fn new_high_entropy() -> Self
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();Sourcepub fn new_v1() -> Self
Available on crate feature rand only.
pub fn new_v1() -> Self
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.
Sourcepub fn new_v1_with_random(random_bits: u128) -> Self
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 au128makes 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);Sourcepub fn new_v0_with_time(time: OffsetDateTime) -> Self
Available on crate features rand and time only.
pub fn new_v0_with_time(time: OffsetDateTime) -> Self
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());Sourcepub fn new_v0_with_parts(epoch_millis: u64, random: u64) -> Self
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);Sourcepub fn as_tnid_string(&self) -> String
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."));Sourcepub fn variant(&self) -> TnidVariant
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);Sourcepub fn to_uuid_string_cased(&self, uppercase: bool) -> String
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: Iftrue, uses uppercase hex digits (A-F). Iffalse, 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"Sourcepub fn parse_uuid_string(uuid_string: &str) -> Option<Self>
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());Sourcepub fn parse_tnid_string(tnid_string: &str) -> Option<Self>
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());Sourcepub fn from_u128(id: u128) -> Option<Self>
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.
Sourcepub fn encrypt_v0_to_v1(
&self,
key: impl Into<EncryptionKey>,
) -> Result<Self, ()>
Available on crate feature encryption only.
pub fn encrypt_v0_to_v1( &self, key: impl Into<EncryptionKey>, ) -> Result<Self, ()>
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());Sourcepub fn decrypt_v1_to_v0(
&self,
key: impl Into<EncryptionKey>,
) -> Result<Self, ()>
Available on crate feature encryption only.
pub fn decrypt_v1_to_v0( &self, key: impl Into<EncryptionKey>, ) -> Result<Self, ()>
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> From<Tnid<Name>> for Uuid
Available on crate feature uuid only.Convert a Tnid to a uuid::Uuid.
impl<Name: TnidName> From<Tnid<Name>> for Uuid
uuid only.Convert a Tnid to a uuid::Uuid.
This conversion is infallible because all Tnids are valid UUIDv8 identifiers by design.
Source§impl<Name: Ord + TnidName> Ord for Tnid<Name>
impl<Name: Ord + TnidName> Ord for Tnid<Name>
1.21.0 · Source§fn max(self, other: Self) -> Selfwhere
Self: Sized,
fn max(self, other: Self) -> Selfwhere
Self: Sized,
Source§impl<Name: PartialOrd + TnidName> PartialOrd for Tnid<Name>
impl<Name: PartialOrd + TnidName> PartialOrd for Tnid<Name>
Source§impl<Name: TnidName> TryFrom<Uuid> for Tnid<Name>
Available on crate feature uuid only.Attempt to convert a uuid::Uuid to a Tnid.
impl<Name: TnidName> TryFrom<Uuid> for Tnid<Name>
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