How do I generate unique identifiers with uuid in Rust?

Walkthrough

The uuid crate provides facilities for generating and parsing Universally Unique Identifiers (UUIDs). A UUID is a 128-bit number used to identify information in computer systems. The crate supports multiple UUID versions: v1 (timestamp-based), v3 (MD5 hash-based), v4 (random), v5 (SHA-1 hash-based), v6 (sortable timestamp-based), and v7 (time-ordered). UUIDs are essential for database primary keys, distributed systems, session identifiers, and anywhere you need unique identifiers without central coordination.

Key concepts:

  1. UUID v4 — randomly generated, most common for general use
  2. UUID v7 — time-ordered, sortable, great for database keys
  3. UUID v5 — deterministic from a namespace and name
  4. Parsing — convert string representations to UUID objects
  5. Serialization — support for various formats (hyphenated, simple, urn, braced)

Code Example

# Cargo.toml
[dependencies]
uuid = { version = "1.0", features = ["v4", "v7", "serde"] }
use uuid::Uuid;
 
fn main() {
    // Generate a random UUID (v4)
    let id = Uuid::new_v4();
    println!("Random UUID: {}", id);
    
    // Generate a time-ordered UUID (v7)
    let time_id = Uuid::now_v7();
    println!("Time-ordered UUID: {}", time_id);
}

Basic UUID Generation

use uuid::Uuid;
 
fn main() {
    // v4: Random UUID (most common)
    let v4 = Uuid::new_v4();
    println!("v4 (random): {}", v4);
    
    // Requires "v7" feature
    let v7 = Uuid::now_v7();
    println!("v7 (time-ordered): {}", v7);
    
    // Nil UUID (all zeros)
    let nil = Uuid::nil();
    println!("Nil: {}", nil);
    
    // Check properties
    println!("Is nil: {}", nil.is_nil());
    println!("Version: {:?}", v4.get_version());
    println!("Variant: {:?}", v4.get_variant());
}

UUID Versions Explained

use uuid::Uuid;
 
fn main() {
    // v1: Timestamp + MAC address (requires "v1" feature)
    // Not recommended due to privacy concerns with MAC address exposure
    // let v1 = Uuid::new_v1(timestamp, node_id);
    
    // v3: MD5 hash of namespace + name (requires "v3" feature)
    // Deterministic - same input always produces same UUID
    // let v3 = Uuid::new_v3(namespace, name);
    
    // v4: Random (requires "v4" feature)
    // Best for general use when you don't need determinism
    let v4 = Uuid::new_v4();
    println!("v4 random: {}", v4);
    
    // v5: SHA-1 hash of namespace + name (requires "v5" feature)
    // Better than v3 (stronger hash), deterministic
    // let v5 = Uuid::new_v5(namespace, name);
    
    // v6: Field-compatible with v1 but sortable (requires "v6" feature)
    // let v6 = Uuid::new_v6(timestamp, node_id);
    
    // v7: Time-ordered (requires "v7" feature)
    // Best for database primary keys - naturally sortable by time
    let v7 = Uuid::now_v7();
    println!("v7 time-ordered: {}", v7);
    
    // v8: Custom UUID (requires "v8" feature)
    // For application-specific UUID formats
    // let v8 = Uuid::new_v8(custom_bytes);
}

Parsing UUIDs from Strings

use uuid::Uuid;
 
fn main() {
    // Parse from standard hyphenated format
    let parsed = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000");
    match parsed {
        Ok(uuid) => println!("Parsed: {}", uuid),
        Err(e) => println!("Parse error: {}", e),
    }
    
    // Parse from simple format (no hyphens)
    let simple = Uuid::parse_str("550e8400e29b41d4a716446655440000");
    println!("Simple parse: {:?}", simple);
    
    // Parse from URN format
    let urn = Uuid::parse_str("urn:uuid:550e8400-e29b-41d4-a716-446655440000");
    println!("URN parse: {:?}", urn);
    
    // Parse from braced format
    let braced = Uuid::parse_str("{550e8400-e29b-41d4-a716-446655440000}");
    println!("Braced parse: {:?}", braced);
    
    // Use try_into for parsing
    let uuid: Result<Uuid, _> = "550e8400-e29b-41d4-a716-446655440000".parse();
    println!("Using parse: {:?}", uuid);
}

UUID String Formats

use uuid::Uuid;
 
fn main() {
    let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
    
    // Default Display (hyphenated, lowercase)
    println!("Hyphenated: {}", uuid);
    
    // Hyphenated, uppercase
    println!("Upper: {}", uuid.hyphenated().to_uppercase());
    
    // Simple (no hyphens)
    println!("Simple: {}", uuid.simple());
    
    // URN format
    println!("URN: {}", uuid.urn());
    
    // Braced format
    println!("Braced: {}", uuid.braced());
}

UUID v5 for Deterministic IDs

use uuid::{Uuid, uuid};
 
fn main() {
    // Use a namespace UUID
    let namespace = Uuid::NAMESPACE_DNS;
    
    // Generate a deterministic UUID from "example.com"
    let uuid1 = Uuid::new_v5(namespace, b"example.com");
    println!("UUID for example.com: {}", uuid1);
    
    // Same input always produces same UUID
    let uuid2 = Uuid::new_v5(namespace, b"example.com");
    assert_eq!(uuid1, uuid2);
    println!("Deterministic: uuid1 == uuid2");
    
    // Different input produces different UUID
    let uuid3 = Uuid::new_v5(namespace, b"different.com");
    assert_ne!(uuid1, uuid3);
    println!("UUID for different.com: {}", uuid3);
    
    // Standard namespaces
    println!("DNS namespace: {}", Uuid::NAMESPACE_DNS);
    println!("URL namespace: {}", Uuid::NAMESPACE_URL);
    println!("OID namespace: {}", Uuid::NAMESPACE_OID);
    println!("X500 namespace: {}", Uuid::NAMESPACE_X500);
}

Custom Namespaces for v5

use uuid::Uuid;
 
fn main() {
    // Create a custom namespace for your application
    let app_namespace = Uuid::new_v4();
    println!("App namespace: {}", app_namespace);
    
    // Now you can generate deterministic UUIDs for your entities
    let user_id = Uuid::new_v5(app_namespace, b"user:12345");
    println!("User 12345 UUID: {}", user_id);
    
    let order_id = Uuid::new_v5(app_namespace, b"order:67890");
    println!("Order 67890 UUID: {}", order_id);
    
    // Same names always produce same UUIDs
    let user_id_again = Uuid::new_v5(app_namespace, b"user:12345");
    assert_eq!(user_id, user_id_again);
}

UUID v7 for Sortable IDs

use uuid::Uuid;
use std::thread;
use std::time::Duration;
 
fn main() {
    let mut uuids = Vec::new();
    
    // Generate several UUIDs over time
    for i in 0..5 {
        let uuid = Uuid::now_v7();
        uuids.push(uuid);
        println!("UUID {}: {}", i, uuid);
        thread::sleep(Duration::from_millis(10));
    }
    
    // v7 UUIDs are sortable by creation time
    let mut sorted = uuids.clone();
    sorted.sort();
    
    println!("\nOriginal order matches sorted order:");
    for (original, sorted) in uuids.iter().zip(sorted.iter()) {
        println!("  {} == {} ? {}", original, sorted, original == sorted);
    }
}

Extracting Timestamp from v7

use uuid::Uuid;
 
fn main() {
    let uuid = Uuid::now_v7();
    println!("UUID v7: {}", uuid);
    
    // Extract the timestamp
    if let Some(ts) = uuid.get_timestamp() {
        println!("Timestamp: {:?}", ts);
        
        // Convert to Unix timestamp
        let seconds = ts.to_unix();
        println!("Unix timestamp: {} seconds, {} nanos", seconds.secs, seconds.nanos);
        
        // Convert to SystemTime
        let time = std::time::SystemTime::from(ts);
        println!("SystemTime: {:?}", time);
    }
}

Binary Representation

use uuid::Uuid;
 
fn main() {
    let uuid = Uuid::new_v4();
    println!("UUID: {}", uuid);
    
    // Get as bytes array
    let bytes = uuid.as_bytes();
    println!("Bytes: {:?}", bytes);
    println!("Length: {} bytes", bytes.len());
    
    // Create from bytes
    let from_bytes = Uuid::from_bytes(*bytes);
    println!("From bytes: {}", from_bytes);
    
    // Get as u128
    let as_u128 = uuid.as_u128();
    println!("As u128: {}", as_u128);
    
    // Create from u128
    let from_u128 = Uuid::from_u128(as_u128);
    println!("From u128: {}", from_u128);
    
    // Get as two u64s
    let (high, low) = uuid.as_u64_pair();
    println!("High: {}, Low: {}", high, low);
}

Serde Serialization

// Cargo.toml:
// uuid = { version = "1.0", features = ["v4", "serde"] }
 
use uuid::Uuid;
use serde::{Serialize, Deserialize};
 
#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: Uuid,
    name: String,
}
 
fn main() {
    let user = User {
        id: Uuid::new_v4(),
        name: "Alice".to_string(),
    };
    
    // Serialize to JSON
    let json = serde_json::to_string(&user).unwrap();
    println!("JSON: {}", json);
    
    // Deserialize from JSON
    let parsed: User = serde_json::from_str(&json).unwrap();
    println!("Parsed: {:?}", parsed);
}

Custom Serde Formats

// Cargo.toml:
// uuid = { version = "1.0", features = ["v4", "serde"] }
 
use serde::{Serialize, Deserialize};
use uuid::Uuid;
 
#[derive(Debug, Serialize, Deserialize)]
struct Document {
    // Default: hyphenated string
    #[serde(default)]
    id: Uuid,
    
    // Simple format (no hyphens)
    #[serde(with = "uuid::serde::simple")]
    simple_id: Uuid,
    
    // URN format
    #[serde(with = "uuid::serde::urn")]
    urn_id: Uuid,
    
    // Braced format
    #[serde(with = "uuid::serde::braced")]
    braced_id: Uuid,
}
 
fn main() {
    let doc = Document {
        id: Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(),
        simple_id: Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(),
        urn_id: Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(),
        braced_id: Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(),
    };
    
    let json = serde_json::to_string_pretty(&doc).unwrap();
    println!("{}", json);
}

Database Integration

// Example with sqlx (requires "sqlx" feature or manual implementation)
use uuid::Uuid;
 
// Hypothetical database record
#[derive(Debug)]
struct User {
    id: Uuid,
    email: String,
    created_at: chrono::DateTime<chrono::Utc>,
}
 
impl User {
    fn new(email: String) -> Self {
        Self {
            id: Uuid::new_v4(), // or Uuid::now_v7() for sortable keys
            email,
            created_at: chrono::Utc::now(),
        }
    }
}
 
fn main() {
    let user = User::new("alice@example.com".to_string());
    
    println!("User ID: {}", user.id);
    println!("Email: {}", user.email);
    println!("Created: {}", user.created_at);
    
    // UUID v7 is ideal for database primary keys
    let id_v7 = Uuid::now_v7();
    println!("\nv7 for database: {}", id_v7);
    println!("Sorts naturally by creation time");
}

Comparing and Sorting UUIDs

use uuid::Uuid;
 
fn main() {
    let uuid1 = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
    let uuid2 = Uuid::parse_str("00000000-0000-0000-0000-000000000002").unwrap();
    let uuid3 = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
    
    // Comparison
    println!("uuid1 == uuid3: {}", uuid1 == uuid3);
    println!("uuid1 < uuid2: {}", uuid1 < uuid2);
    
    // Sorting
    let mut uuids = vec![uuid2, uuid1, uuid3];
    uuids.sort();
    uuids.dedup(); // Remove duplicates
    
    println!("Sorted and deduped: {:?}", uuids);
    
    // Use in collections
    let mut map = std::collections::HashMap::new();
    let key = Uuid::new_v4();
    map.insert(key, "value");
    
    println!("Map value: {:?}", map.get(&key));
}

UUID in Structs

use uuid::Uuid;
 
#[derive(Debug, Clone)]
struct Entity {
    id: Uuid,
    name: String,
}
 
impl Entity {
    fn new(name: String) -> Self {
        Self {
            id: Uuid::new_v4(),
            name,
        }
    }
    
    fn with_id(id: Uuid, name: String) -> Self {
        Self { id, name }
    }
}
 
fn main() {
    let entity = Entity::new("Test Entity".to_string());
    println!("Entity: {:?}", entity);
    
    // Recreate from stored UUID
    let stored_id = entity.id;
    let restored = Entity::with_id(stored_id, "Restored Entity".to_string());
    println!("Restored: {:?}", restored);
}

Compile-Time UUID Constants

use uuid::uuid;
 
// Define UUID constants at compile time
const NAMESPACE_DNS: Uuid = uuid!("6ba7b810-9dad-11d1-80b4-00c04fd430c8");
const MY_APP_ID: Uuid = uuid!("550e8400-e29b-41d4-a716-446655440000");
 
fn main() {
    println!("DNS Namespace: {}", NAMESPACE_DNS);
    println!("App ID: {}", MY_APP_ID);
    
    // Use as namespace for v5
    let user_uuid = Uuid::new_v5(MY_APP_ID, b"user:42");
    println!("User UUID: {}", user_uuid);
}

Bulk Generation

use uuid::Uuid;
 
fn main() {
    // Generate multiple UUIDs
    let uuids: Vec<Uuid> = (0..10).map(|_| Uuid::new_v4()).collect();
    
    println!("Generated {} UUIDs:", uuids.len());
    for (i, uuid) in uuids.iter().enumerate() {
        println!("  {}: {}", i, uuid);
    }
    
    // Check uniqueness
    let mut unique = uuids.clone();
    unique.sort();
    unique.dedup();
    
    println!("\nAll unique: {}", unique.len() == uuids.len());
}

UUID Builder

use uuid::{Uuid, Builder};
 
fn main() {
    // Build UUID from bytes
    let bytes: [u8; 16] = [
        0x55, 0x0e, 0x84, 0x00,
        0xe2, 0x9b,
        0x41, 0xd4,
        0xa7, 0x16,
        0x44, 0x66, 0x55, 0x44, 0x00, 0x00,
    ];
    
    let uuid = Builder::from_bytes(bytes).into_uuid();
    println!("Built: {}", uuid);
    
    // Build from random bytes and set version
    let mut random_bytes = [0u8; 16];
    // In real code, use a proper RNG
    for (i, byte) in random_bytes.iter_mut().enumerate() {
        *byte = i as u8;
    }
    
    let uuid = Builder::from_random_bytes(random_bytes).into_uuid();
    println!("From random bytes: {}", uuid);
}

Error Handling

use uuid::Uuid;
 
fn parse_uuid(input: &str) -> Result<Uuid, String> {
    Uuid::parse_str(input).map_err(|e| format!("Invalid UUID '{}': {}", input, e))
}
 
fn main() {
    // Valid UUIDs
    match parse_uuid("550e8400-e29b-41d4-a716-446655440000") {
        Ok(uuid) => println!("Parsed: {}", uuid),
        Err(e) => println!("Error: {}", e),
    }
    
    // Invalid UUIDs
    match parse_uuid("not-a-uuid") {
        Ok(uuid) => println!("Parsed: {}", uuid),
        Err(e) => println!("Error: {}", e),
    }
    
    match parse_uuid("550e8400-e29b-41d4-a716-44665544000") { // Too short
        Ok(uuid) => println!("Parsed: {}", uuid),
        Err(e) => println!("Error: {}", e),
    }
}

Session ID Example

use uuid::Uuid;
use std::collections::HashMap;
 
struct Session {
    id: Uuid,
    user_id: String,
    created_at: std::time::Instant,
}
 
impl Session {
    fn new(user_id: String) -> Self {
        Self {
            id: Uuid::new_v4(),
            user_id,
            created_at: std::time::Instant::now(),
        }
    }
}
 
struct SessionManager {
    sessions: HashMap<Uuid, Session>,
}
 
impl SessionManager {
    fn new() -> Self {
        Self { sessions: HashMap::new() }
    }
    
    fn create_session(&mut self, user_id: String) -> Uuid {
        let session = Session::new(user_id);
        let id = session.id;
        self.sessions.insert(id, session);
        id
    }
    
    fn get_session(&self, id: &Uuid) -> Option<&Session> {
        self.sessions.get(id)
    }
    
    fn remove_session(&mut self, id: &Uuid) -> bool {
        self.sessions.remove(id).is_some()
    }
}
 
fn main() {
    let mut manager = SessionManager::new();
    
    // Create session
    let session_id = manager.create_session("user_123".to_string());
    println!("Created session: {}", session_id);
    
    // Get session
    if let Some(session) = manager.get_session(&session_id) {
        println!("User: {}", session.user_id);
    }
    
    // Remove session
    manager.remove_session(&session_id);
    println!("Session removed");
}

Real-World Entity IDs

use uuid::Uuid;
 
// Namespace for this application's entities
const APP_NAMESPACE: Uuid = uuid::uuid!("f47ac10b-58cc-4372-a567-0e02b2c3d479");
 
#[derive(Debug)]
struct Order {
    id: Uuid,
    customer_id: Uuid,
    items: Vec<String>,
}
 
impl Order {
    fn new(customer_id: Uuid) -> Self {
        Self {
            id: Uuid::new_v4(), // Random ID for the order
            customer_id,
            items: Vec::new(),
        }
    }
    
    fn add_item(&mut self, item: String) {
        self.items.push(item);
    }
}
 
#[derive(Debug)]
struct Customer {
    id: Uuid,
    email: String,
    name: String,
}
 
impl Customer {
    fn new(email: String, name: String) -> Self {
        // Use v5 for deterministic customer ID from email
        let id = Uuid::new_v5(APP_NAMESPACE, email.as_bytes());
        Self { id, email, name }
    }
}
 
fn main() {
    // Create customer (same email = same ID)
    let customer = Customer::new("alice@example.com".to_string(), "Alice".to_string());
    println!("Customer: {:?}", customer);
    
    // Create order
    let mut order = Order::new(customer.id);
    order.add_item("Widget".to_string());
    order.add_item("Gadget".to_string());
    
    println!("Order: {:?}", order);
}

Summary

  • Uuid::new_v4() generates random UUIDs — most common for general use
  • Uuid::now_v7() generates time-ordered UUIDs — ideal for sortable database keys
  • Uuid::new_v5(namespace, name) generates deterministic UUIDs from a name
  • Parse UUIDs with Uuid::parse_str() or .parse()
  • Standard formats: hyphenated, simple, urn, braced
  • Extract binary data with as_bytes(), as_u128(), or as_u64_pair()
  • Use uuid!() macro for compile-time UUID constants
  • Serde integration with #[serde(with = "uuid::serde::simple")] for custom formats
  • Standard namespaces: NAMESPACE_DNS, NAMESPACE_URL, NAMESPACE_OID, NAMESPACE_X500
  • UUID v7 is sortable and extractable timestamp
  • Enable features: v4, v5, v7, serde as needed
  • Perfect for: primary keys, session IDs, request IDs, entity identifiers, distributed systems