How do I generate and parse UUIDs in Rust?

Walkthrough

The uuid crate provides a complete implementation of Universally Unique Identifiers (UUIDs) as defined in RFC 4122. UUIDs are 128-bit identifiers that are practically unique without requiring a central authority. They're commonly used for database primary keys, session identifiers, distributed system IDs, and anywhere you need unique identifiers that don't need to be sequential.

Key concepts:

  1. UUID versions — v1 (timestamp + MAC), v3 (MD5 hash), v4 (random), v5 (SHA-1 hash), v7 (timestamp + random)
  2. Parsing — convert string representation to UUID
  3. Formatting — hyphenated, simple (no hyphens), urn format
  4. Byte representation — work with raw 16-byte arrays
  5. Nil UUID — special all-zeros UUID for sentinels

Code Example

# Cargo.toml
[dependencies]
uuid = { version = "1.0", features = ["v4", "fast-rng"] }
use uuid::Uuid;
 
fn main() {
    // Generate a random UUID (v4)
    let id = Uuid::new_v4();
    println!("Random UUID: {}", id);
    
    // Parse from string
    let parsed = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
    println!("Parsed UUID: {}", parsed);
    
    // Get version
    println!("Version: {:?}", parsed.get_version());
}

Generating Different UUID Versions

use uuid::Uuid;
 
fn main() {
    // v4: Random UUID (most common)
    let v4 = Uuid::new_v4();
    println!("v4 (random): {}", v4);
    
    // v7: Time-ordered UUID (newer, recommended for databases)
    // Requires "v7" feature
    #[cfg(feature = "v7")]
    {
        let v7 = Uuid::now_v7();
        println!("v7 (time-ordered): {}", v7);
    }
    
    // v3: Name-based (MD5)
    // Requires "v3" feature
    #[cfg(feature = "v3")]
    {
        let namespace = Uuid::NAMESPACE_DNS;
        let v3 = Uuid::new_v3(namespace, b"example.com");
        println!("v3 (MD5): {}", v3);
    }
    
    // v5: Name-based (SHA-1)
    // Requires "v5" feature
    #[cfg(feature = "v5")]
    {
        let namespace = Uuid::NAMESPACE_DNS;
        let v5 = Uuid::new_v5(namespace, b"example.com");
        println!("v5 (SHA-1): {}", v5);
    }
    
    // Nil UUID (all zeros)
    let nil = Uuid::nil();
    println!("Nil UUID: {}", nil);
    println!("Is nil: {}", nil.is_nil());
}

Parsing UUIDs

use uuid::Uuid;
 
fn main() {
    // Parse standard hyphenated format
    let from_hyphenated = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
    println!("Parsed: {}", from_hyphenated);
    
    // Parse simple format (no hyphens)
    let from_simple = Uuid::parse_str("550e8400e29b41d4a716446655440000").unwrap();
    println!("From simple: {}", from_simple);
    
    // Parse URN format
    let from_urn = Uuid::parse_str("urn:uuid:550e8400-e29b-41d4-a716-446655440000").unwrap();
    println!("From URN: {}", from_urn);
    
    // Parse with braces
    let from_braced = Uuid::parse_str("{550e8400-e29b-41d4-a716-446655440000}").unwrap();
    println!("From braced: {}", from_braced);
    
    // All parsed to the same UUID
    assert_eq!(from_hyphenated, from_simple);
    assert_eq!(from_hyphenated, from_urn);
    assert_eq!(from_hyphenated, from_braced);
    
    // Handle parse errors
    match Uuid::parse_str("invalid-uuid") {
        Ok(uuid) => println!("Parsed: {}", uuid),
        Err(e) => println!("Parse error: {}", e),
    }
}

UUID String Formats

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

Working with Bytes

use uuid::Uuid;
 
fn main() {
    let uuid = Uuid::new_v4();
    
    // Convert to bytes (16-byte array)
    let bytes = uuid.as_bytes();
    println!("Bytes: {:02x?}", bytes);
    println!("Length: {} bytes", bytes.len());
    
    // Create from bytes
    let from_bytes = Uuid::from_bytes(bytes);
    assert_eq!(uuid, from_bytes);
    
    // Create from bytes slice
    let bytes_slice: [u8; 16] = [
        0x55, 0x0e, 0x84, 0x00,
        0xe2, 0x9b,
        0x41, 0xd4,
        0xa7, 0x16,
        0x44, 0x66, 0x55, 0x44, 0x00, 0x00,
    ];
    let from_slice = Uuid::from_bytes_ref(&bytes_slice);
    println!("From slice: {}", from_slice);
    
    // Convert to u128
    let as_u128 = uuid.as_u128();
    println!("As u128: {}", as_u128);
    
    // Create from u128
    let from_u128 = Uuid::from_u128(as_u128);
    assert_eq!(uuid, from_u128);
    
    // Convert to two u64 values (high and low)
    let (high, low) = (uuid.as_u64_pair());
    println!("High: {}, Low: {}", high, low);
}

UUID Properties and Metadata

use uuid::{Uuid, Version, Variant};
 
fn main() {
    let v4 = Uuid::new_v4();
    println!("v4 UUID: {}", v4);
    
    // Get version
    println!("Version: {:?}", v4.get_version());
    
    // Get variant
    println!("Variant: {:?}", v4.get_variant());
    
    // Check version-specific properties
    match v4.get_version() {
        Some(Version::Random) => println!("This is a random UUID"),
        Some(Version::Mac) => println!("This is a MAC-based UUID"),
        Some(Version::Md5) => println!("This is an MD5 name-based UUID"),
        Some(Version::Sha1) => println!("This is a SHA-1 name-based UUID"),
        Some(Version::SortRand) => println!("This is a time-ordered UUID"),
        _ => println!("Other version"),
    }
    
    // Compare UUIDs
    let another = Uuid::new_v4();
    println!("\nComparing UUIDs:");
    println!("Equal: {}", v4 == another);
    println!("Not equal: {}", v4 != another);
    
    // Ordering
    let uuids = vec![another, v4];
    let mut sorted = uuids;
    sorted.sort();
    println!("Sorted: {}", sorted[0] < sorted[1]);
}

Name-Based UUIDs (v3 and v5)

use uuid::Uuid;
 
fn main() {
    // Standard namespaces
    println!("Standard namespaces:");
    println!("  DNS: {}", Uuid::NAMESPACE_DNS);
    println!("  URL: {}", Uuid::NAMESPACE_URL);
    println!("  OID: {}", Uuid::NAMESPACE_OID);
    println!("  X500: {}", Uuid::NAMESPACE_X500);
    
    // v5: SHA-1 based (recommended over v3)
    let user_id = Uuid::new_v5(Uuid::NAMESPACE_DNS, b"user@example.com");
    println!("\nUser ID (v5): {}", user_id);
    
    // Same input = same UUID
    let same_user_id = Uuid::new_v5(Uuid::NAMESPACE_DNS, b"user@example.com");
    assert_eq!(user_id, same_user_id);
    println!("Deterministic: same input always produces same UUID");
    
    // Different namespace = different UUID
    let url_user_id = Uuid::new_v5(Uuid::NAMESPACE_URL, b"user@example.com");
    assert_ne!(user_id, url_user_id);
    println!("Different namespace produces different UUID");
    
    // Custom namespace
    let my_namespace = Uuid::new_v4();
    let custom_id = Uuid::new_v5(my_namespace, b"my-data");
    println!("\nCustom namespace: {}", my_namespace);
    println!("Custom ID: {}", custom_id);
    
    // Example: Generate consistent IDs for records
    let records = vec!["alice", "bob", "charlie"];
    let namespace = Uuid::NAMESPACE_DNS;
    
    println!("\nRecord IDs:");
    for record in records {
        let id = Uuid::new_v5(namespace, record.as_bytes());
        println!("  {}: {}", record, id);
    }
}

Using in Structs

use uuid::Uuid;
use std::collections::HashMap;
 
#[derive(Debug, Clone)]
struct User {
    id: Uuid,
    username: String,
    email: String,
}
 
impl User {
    fn new(username: String, email: String) -> Self {
        Self {
            id: Uuid::new_v4(),
            username,
            email,
        }
    }
    
    fn with_id(id: Uuid, username: String, email: String) -> Self {
        Self { id, username, email }
    }
}
 
struct UserStore {
    users: HashMap<Uuid, User>,
}
 
impl UserStore {
    fn new() -> Self {
        Self {
            users: HashMap::new(),
        }
    }
    
    fn create_user(&mut self, username: String, email: String) -> Uuid {
        let user = User::new(username, email);
        let id = user.id;
        self.users.insert(id, user);
        id
    }
    
    fn get_user(&self, id: &Uuid) -> Option<&User> {
        self.users.get(id)
    }
    
    fn delete_user(&mut self, id: &Uuid) -> Option<User> {
        self.users.remove(id)
    }
}
 
fn main() {
    let mut store = UserStore::new();
    
    // Create users
    let alice_id = store.create_user("alice".to_string(), "alice@example.com".to_string());
    let bob_id = store.create_user("bob".to_string(), "bob@example.com".to_string());
    
    println!("Created users:");
    println!("  Alice: {}", alice_id);
    println!("  Bob: {}", bob_id);
    
    // Get user
    if let Some(user) = store.get_user(&alice_id) {
        println!("\nFound: {:?}", user);
    }
    
    // Parse ID from string
    let id_str = alice_id.to_string();
    if let Ok(parsed_id) = Uuid::parse_str(&id_str) {
        if let Some(user) = store.get_user(&parsed_id) {
            println!("Found by parsed ID: {}", user.username);
        }
    }
}

Serialization with Serde

// Requires: uuid = { version = "1.0", features = ["v4", "serde"] }
 
use uuid::Uuid;
use serde::{Serialize, Deserialize};
 
#[derive(Debug, Serialize, Deserialize)]
struct Document {
    id: Uuid,
    title: String,
    content: String,
}
 
fn main() {
    // Create document
    let doc = Document {
        id: Uuid::new_v4(),
        title: "My Document".to_string(),
        content: "Hello, World!".to_string(),
    };
    
    // Serialize to JSON
    let json = serde_json::to_string(&doc).unwrap();
    println!("JSON: {}", json);
    
    // Deserialize from JSON
    let parsed: Document = serde_json::from_str(&json).unwrap();
    println!("Parsed: {:?}", parsed);
    
    // The UUID is serialized as a string by default
    assert!(json.contains('"'));
}

Database Integration

use uuid::Uuid;
 
// Simulated database operations
#[derive(Debug, Clone)]
struct Record {
    id: Uuid,
    data: String,
}
 
struct Database {
    records: Vec<Record>,
}
 
impl Database {
    fn new() -> Self {
        Self { records: Vec::new() }
    }
    
    fn insert(&mut self, data: String) -> Uuid {
        let record = Record {
            id: Uuid::new_v4(),
            data,
        };
        let id = record.id;
        self.records.push(record);
        id
    }
    
    fn find(&self, id: Uuid) -> Option<&Record> {
        self.records.iter().find(|r| r.id == id)
    }
    
    fn delete(&mut self, id: Uuid) -> bool {
        let len = self.records.len();
        self.records.retain(|r| r.id != id);
        self.records.len() < len
    }
}
 
fn main() {
    let mut db = Database::new();
    
    // Insert records
    let id1 = db.insert("First record".to_string());
    let id2 = db.insert("Second record".to_string());
    
    println!("Inserted records:");
    println!("  ID 1: {}", id1);
    println!("  ID 2: {}", id2);
    
    // Find record
    if let Some(record) = db.find(id1) {
        println!("\nFound: {:?}", record);
    }
    
    // Delete record
    if db.delete(id1) {
        println!("\nDeleted record {}", id1);
    }
    
    // Try to find deleted record
    if db.find(id1).is_none() {
        println!("Record not found (expected)");
    }
}

Bulk Generation

use uuid::Uuid;
 
fn main() {
    // Generate multiple UUIDs
    let uuids: Vec<Uuid> = (0..5).map(|_| Uuid::new_v4()).collect();
    
    println!("Generated {} UUIDs:", uuids.len());
    for (i, id) in uuids.iter().enumerate() {
        println!("  {}: {}", i, id);
    }
    
    // Check uniqueness
    let mut seen = std::collections::HashSet::new();
    let all_unique = uuids.iter().all(|id| seen.insert(*id));
    println!("\nAll unique: {}", all_unique);
    
    // Performance benchmark
    use std::time::Instant;
    let count = 100_000;
    
    let start = Instant::now();
    let _: Vec<Uuid> = (0..count).map(|_| Uuid::new_v4()).collect();
    let duration = start.elapsed();
    
    println!("\nGenerated {} UUIDs in {:?}", count, duration);
    println!("Rate: {:.0} UUIDs/sec", count as f64 / duration.as_secs_f64());
}

Validating UUIDs

use uuid::Uuid;
 
fn is_valid_uuid(s: &str) -> bool {
    Uuid::parse_str(s).is_ok()
}
 
fn validate_and_parse(s: &str) -> Result<Uuid, String> {
    Uuid::parse_str(s).map_err(|e| format!("Invalid UUID: {}", e))
}
 
fn main() {
    let test_cases = vec![
        "550e8400-e29b-41d4-a716-446655440000",  // Valid
        "550e8400e29b41d4a716446655440000",       // Valid (simple)
        "urn:uuid:550e8400-e29b-41d4-a716-446655440000",  // Valid (URN)
        "invalid-uuid",                            // Invalid
        "550e8400-e29b-41d4-a716",                // Invalid (too short)
        "550e8400-e29b-41d4-a716-446655440000ZZ",  // Invalid (extra chars)
    ];
    
    println!("UUID Validation:");
    for test in test_cases {
        match validate_and_parse(test) {
            Ok(uuid) => println!("  Valid: {}", uuid),
            Err(e) => println!("  Invalid '{}': {}", test, e),
        }
    }
}

Real-World Example: Session Management

use uuid::Uuid;
use std::collections::HashMap;
use std::time::{Duration, Instant};
 
#[derive(Clone)]
struct Session {
    id: Uuid,
    user_id: Uuid,
    created_at: Instant,
    expires_after: Duration,
    data: HashMap<String, String>,
}
 
impl Session {
    fn new(user_id: Uuid, ttl: Duration) -> Self {
        Self {
            id: Uuid::new_v4(),
            user_id,
            created_at: Instant::now(),
            expires_after: ttl,
            data: HashMap::new(),
        }
    }
    
    fn is_expired(&self) -> bool {
        Instant::now().duration_since(self.created_at) > self.expires_after
    }
}
 
struct SessionManager {
    sessions: HashMap<Uuid, Session>,
    default_ttl: Duration,
}
 
impl SessionManager {
    fn new() -> Self {
        Self {
            sessions: HashMap::new(),
            default_ttl: Duration::from_secs(3600), // 1 hour
        }
    }
    
    fn create_session(&mut self, user_id: Uuid) -> Uuid {
        let session = Session::new(user_id, self.default_ttl);
        let id = session.id;
        self.sessions.insert(id, session);
        id
    }
    
    fn get_session(&mut self, session_id: &Uuid) -> Option<&Session> {
        // Clean up expired sessions
        self.sessions.retain(|_, s| !s.is_expired());
        self.sessions.get(session_id)
    }
    
    fn get_session_mut(&mut self, session_id: &Uuid) -> Option<&mut Session> {
        self.sessions.retain(|_, s| !s.is_expired());
        self.sessions.get_mut(session_id)
    }
    
    fn destroy_session(&mut self, session_id: &Uuid) -> bool {
        self.sessions.remove(session_id).is_some()
    }
    
    fn cleanup_expired(&mut self) -> usize {
        let before = self.sessions.len();
        self.sessions.retain(|_, s| !s.is_expired());
        before - self.sessions.len()
    }
}
 
fn main() {
    let mut manager = SessionManager::new();
    
    // Create user ID
    let user_id = Uuid::new_v4();
    println!("User ID: {}", user_id);
    
    // Create session
    let session_id = manager.create_session(user_id);
    println!("Session ID: {}", session_id);
    
    // Access session
    if let Some(session) = manager.get_session(&session_id) {
        println!("Session valid for user: {}", session.user_id);
    }
    
    // Set session data
    if let Some(session) = manager.get_session_mut(&session_id) {
        session.data.insert("theme".to_string(), "dark".to_string());
        session.data.insert("language".to_string(), "en".to_string());
    }
    
    // Get session data
    if let Some(session) = manager.get_session(&session_id) {
        println!("Theme: {:?}", session.data.get("theme"));
    }
    
    // Destroy session
    manager.destroy_session(&session_id);
    println!("Session destroyed");
    
    if manager.get_session(&session_id).is_none() {
        println!("Session no longer exists");
    }
}

Real-World Example: Event System

use uuid::Uuid;
use std::time::{SystemTime, UNIX_EPOCH};
 
#[derive(Debug, Clone)]
struct Event {
    id: Uuid,
    event_type: String,
    timestamp: u64,
    payload: String,
}
 
impl Event {
    fn new(event_type: &str, payload: String) -> Self {
        Self {
            id: Uuid::new_v4(),
            event_type: event_type.to_string(),
            timestamp: SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs(),
            payload,
        }
    }
}
 
struct EventBus {
    events: Vec<Event>,
    subscribers: Vec<Box<dyn Fn(&Event)>>,
}
 
impl EventBus {
    fn new() -> Self {
        Self {
            events: Vec::new(),
            subscribers: Vec::new(),
        }
    }
    
    fn subscribe<F: Fn(&Event) + 'static>(&mut self, handler: F) {
        self.subscribers.push(Box::new(handler));
    }
    
    fn publish(&mut self, event_type: &str, payload: String) -> Uuid {
        let event = Event::new(event_type, payload);
        let id = event.id;
        
        // Notify subscribers
        for handler in &self.subscribers {
            handler(&event);
        }
        
        self.events.push(event);
        id
    }
    
    fn get_event(&self, id: &Uuid) -> Option<&Event> {
        self.events.iter().find(|e| e.id == *id)
    }
    
    fn events_by_type(&self, event_type: &str) -> Vec<&Event> {
        self.events.iter()
            .filter(|e| e.event_type == event_type)
            .collect()
    }
}
 
fn main() {
    let mut bus = EventBus::new();
    
    // Subscribe to events
    bus.subscribe(|event| {
        println!("[Logger] Event {}: {}", event.id, event.event_type);
    });
    
    bus.subscribe(|event| {
        if event.event_type == "user.created" {
            println!("[Welcome] Sending welcome email for event {}", event.id);
        }
    });
    
    // Publish events
    let id1 = bus.publish("user.created", "{\"user_id\": \"alice\"}".to_string());
    println!("Created event: {}", id1);
    
    let id2 = bus.publish("user.login", "{\"user_id\": \"alice\"}".to_string());
    println!("Created event: {}", id2);
    
    // Retrieve event
    if let Some(event) = bus.get_event(&id1) {
        println!("\nRetrieved event: {:?}", event);
    }
}

Error Handling

use uuid::Uuid;
use std::fmt;
 
#[derive(Debug)]
enum UuidError {
    InvalidFormat(String),
    InvalidVersion(String),
}
 
impl fmt::Display for UuidError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            UuidError::InvalidFormat(s) => write!(f, "Invalid UUID format: {}", s),
            UuidError::InvalidVersion(s) => write!(f, "Invalid UUID version: {}", s),
        }
    }
}
 
impl std::error::Error for UuidError {}
 
fn parse_user_uuid(s: &str) -> Result<Uuid, UuidError> {
    let uuid = Uuid::parse_str(s)
        .map_err(|e| UuidError::InvalidFormat(e.to_string()))?;
    
    // Validate it's a v4 UUID
    match uuid.get_version() {
        Some(uuid::Version::Random) => Ok(uuid),
        _ => Err(UuidError::InvalidVersion("Expected v4 UUID".to_string())),
    }
}
 
fn main() {
    // Valid v4 UUID
    let v4_string = Uuid::new_v4().to_string();
    match parse_user_uuid(&v4_string) {
        Ok(uuid) => println!("Valid v4 UUID: {}", uuid),
        Err(e) => println!("Error: {}", e),
    }
    
    // Valid but not v4
    let v5_string = Uuid::new_v5(Uuid::NAMESPACE_DNS, b"test").to_string();
    match parse_user_uuid(&v5_string) {
        Ok(uuid) => println!("Valid v4 UUID: {}", uuid),
        Err(e) => println!("Error: {}", e),
    }
    
    // Invalid format
    match parse_user_uuid("not-a-uuid") {
        Ok(uuid) => println!("Valid v4 UUID: {}", uuid),
        Err(e) => println!("Error: {}", e),
    }
}

Summary

  • Uuid::new_v4() generates a random UUID (most common, requires v4 feature)
  • Uuid::new_v5(namespace, name) creates deterministic name-based UUIDs (requires v5 feature)
  • Uuid::parse_str(s) parses UUID from string (accepts hyphenated, simple, URN, braced formats)
  • uuid.to_string() returns hyphenated format; uuid.simple() removes hyphens
  • uuid.as_bytes() returns the 16-byte array representation
  • uuid.get_version() returns the UUID version (Random, Mac, Md5, Sha1, etc.)
  • Uuid::nil() returns the nil UUID (all zeros)
  • Enable serde feature for automatic JSON serialization
  • Use v5 (SHA-1) over v3 (MD5) for name-based UUIDs — better collision resistance
  • v7 UUIDs are time-ordered, ideal for database primary keys (requires v7 feature)
  • Standard namespaces: NAMESPACE_DNS, NAMESPACE_URL, NAMESPACE_OID, NAMESPACE_X500