entid
A Rust library for generating and validating type-safe, prefixed entity identifiers based on UUIDs and ULIDs.

Features
- Type-safe entity IDs: Create distinct ID types for different entities
- Multiple identifier formats: Support for both UUID and ULID
- Prefix support: Automatically add entity-specific prefixes to IDs
- Performance optimized: Thread-safe caching of string representations
- Serde compatible: Seamless serialization and deserialization
- Comprehensive error handling: Clear error types for all operations
- Zero-cost abstractions: Minimal runtime overhead
Installation
Add this to your Cargo.toml:
[dependencies]
entid = "0.1.0"
Usage
Basic Example with UUID
use entid::{EntityId, Prefix, UuidIdentifier, UuidEntityId};
struct User;
impl Prefix for User {
fn prefix() -> &'static str {
"user"
}
}
struct Post;
impl Prefix for Post {
fn prefix() -> &'static str {
"post"
}
fn delimiter() -> &'static str {
"-"
}
}
fn main() {
let user_id = UuidEntityId::<User>::generate();
let post_id = EntityId::<Post, UuidIdentifier>::generate();
println!("User ID: {}", user_id); println!("Post ID: {}", post_id);
let parsed_user_id = UuidEntityId::<User>::new("user_123e4567-e89b-12d3-a456-426614174000").unwrap();
}
Using ULID Instead of UUID
use entid::{EntityId, Prefix, UlidIdentifier, UlidEntityId};
struct Product;
impl Prefix for Product {
fn prefix() -> &'static str {
"prod"
}
}
fn main() {
let product_id = UlidEntityId::<Product>::generate();
let product_ids: Vec<UlidEntityId<Product>> = (0..10)
.map(|_| UlidEntityId::<Product>::generate())
.collect();
let mut sorted_ids = product_ids.clone();
sorted_ids.sort();
if let Some(timestamp_ms) = product_id.timestamp_ms() {
println!("Product ID created at: {} ms since epoch", timestamp_ms);
}
}
Using Deterministic UUIDs (v5)
use entid::{EntityId, Prefix, UuidIdentifier, Uuid};
struct ApiKey;
impl Prefix for ApiKey {
fn prefix() -> &'static str {
"key"
}
}
fn main() {
let namespace = Uuid::parse_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
let uuid_id = UuidIdentifier::new_v5(&namespace, "user@example.com");
let api_key = EntityId::<ApiKey, UuidIdentifier>::from_identifier(uuid_id);
let uuid_id2 = UuidIdentifier::new_v5(&namespace, "user@example.com");
let api_key2 = EntityId::<ApiKey, UuidIdentifier>::from_identifier(uuid_id2);
assert_eq!(api_key, api_key2);
}
Error Handling
use entid::{EntityId, EntityIdError, IdentifierError, Prefix, UuidIdentifier};
struct User;
impl Prefix for User {
fn prefix() -> &'static str {
"user"
}
}
fn parse_id(input: &str) -> Result<(), Box<dyn std::error::Error>> {
match EntityId::<User, UuidIdentifier>::new(input) {
Ok(id) => {
println!("Successfully parsed ID: {}", id);
Ok(())
},
Err(EntityIdError::InvalidFormat) => {
println!("Invalid ID format: {}", input);
Err(Box::new(EntityIdError::InvalidFormat))
},
Err(EntityIdError::InvalidIdentifier) => {
println!("Invalid identifier part in ID: {}", input);
Err(Box::new(EntityIdError::InvalidIdentifier))
}
}
}
fn parse_raw_identifier(input: &str) -> Result<UuidIdentifier, IdentifierError> {
UuidIdentifier::parse(input)
}
Using with Serde
use entid::{EntityId, Prefix, UlidIdentifier};
use serde::{Serialize, Deserialize};
struct Order;
impl Prefix for Order {
fn prefix() -> &'static str {
"order"
}
}
#[derive(Serialize, Deserialize)]
struct OrderRecord {
id: EntityId<Order, UlidIdentifier>,
customer_name: String,
amount: f64,
}
fn main() {
let order = OrderRecord {
id: EntityId::<Order, UlidIdentifier>::generate(),
customer_name: "John Doe".to_string(),
amount: 123.45,
};
let json = serde_json::to_string(&order).unwrap();
println!("JSON: {}", json);
let deserialized: OrderRecord = serde_json::from_str(&json).unwrap();
assert_eq!(order.id, deserialized.id);
}
Using with Databases
use entid::{EntityId, Prefix, UuidIdentifier};
struct Customer;
impl Prefix for Customer {
fn prefix() -> &'static str {
"cust"
}
}
fn store_in_db(customer_id: &EntityId<Customer, UuidIdentifier>, name: &str) {
let id_str = customer_id.as_str();
let uuid = customer_id.identifier().uuid();
}
fn retrieve_from_db(id_str: &str) -> Result<EntityId<Customer, UuidIdentifier>, entid::EntityIdError> {
EntityId::<Customer, UuidIdentifier>::new(id_str)
}
Advanced Usage
Creating Monotonic ULIDs
use entid::{EntityId, Prefix, UlidIdentifier};
struct Task;
impl Prefix for Task {
fn prefix() -> &'static str {
"task"
}
}
fn main() {
let task1 = EntityId::<Task, UlidIdentifier>::generate();
let ulid2 = UlidIdentifier::monotonic_from(Some(task1.identifier()));
let task2 = EntityId::<Task, UlidIdentifier>::from_identifier(ulid2);
assert!(task2 > task1);
}
Custom Validation
use entid::{EntityId, Prefix, UuidIdentifier};
struct ApiKey;
impl Prefix for ApiKey {
fn prefix() -> &'static str {
"key"
}
}
impl EntityId<ApiKey, UuidIdentifier> {
pub fn is_valid_for_environment(&self, env: &str) -> bool {
match env {
"production" => self.identifier().version() == Some(uuid::Version::Sha1),
_ => true,
}
}
}
Choosing Between UUID and ULID
UUID Advantages
- Industry standard with wide adoption
- Multiple versions for different use cases (v1, v3, v4, v5)
- Well-supported in databases and other systems
ULID Advantages
- Lexicographically sortable (sorts by creation time)
- URL-safe (no special characters)
- Shorter string representation (26 characters vs 36 for UUID)
- Built-in timestamp component
Performance Considerations
- String representations are cached using
OnceLock for thread-safe lazy initialization
- The
EntityId type implements Hash, PartialEq, and Eq for efficient use in collections
- Memory usage is optimized by using
PhantomData for type parameters
License
This project is licensed under the MIT License - see the LICENSE file for details.