How do I generate UUIDs in Rust?

Walkthrough

The uuid crate provides generation and parsing of Universally Unique Identifiers (UUIDs). UUIDs are 128-bit identifiers used for unique keys, database primary keys, tracking identifiers, and distributed systems. The crate supports all UUID versions including v1 (timestamp/MAC), v3 (MD5 hash), v4 (random), v5 (SHA-1 hash), and v7 (time-ordered). Version 4 (random) is the most commonly used for general purposes.

Key concepts:

  1. UUID v1 — timestamp + MAC address (requires v1 feature)
  2. UUID v3 — MD5 hash of namespace + name (requires v3 feature)
  3. UUID v4 — random (requires v4 feature)
  4. UUID v5 — SHA-1 hash of namespace + name (requires v5 feature)
  5. UUID v7 — time-ordered (requires v7 feature)
  6. Parsing — from string representation
  7. Format — hyphenated, simple, urn, braced

Code Example

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

Generating UUIDs

use uuid::Uuid;
 
fn main() {
    // v4 - Random UUID (most common)
    let id1 = Uuid::new_v4();
    println!("v4: {}", id1);
    
    let id2 = Uuid::new_v4();
    println!("Another v4: {}", id2);
    
    // Each call generates a different UUID
    println!("Equal: {}", id1 == id2);
    
    // nil UUID (all zeros)
    let nil = Uuid::nil();
    println!("nil: {}", nil);
    
    // max UUID (all ones)
    let max = Uuid::max();
    println!("max: {}", max);
}
 
// With v1 feature enabled in Cargo.toml:
// [dependencies]
// uuid = { version = "1", features = ["v1"] }
 
fn v1_example() {
    use uuid::Uuid;
    
    // v1 requires a timestamp and node ID
    // This is typically done with a context provider
    // v1 UUIDs are less common due to privacy concerns with MAC addresses
}
 
// With v3 feature enabled:
// [dependencies]
// uuid = { version = "1", features = ["v3"] }
 
fn v3_example() {
    use uuid::Uuid;
    
    // v3 - MD5 hash based
    // Same namespace + name always produces same UUID
    let namespace = Uuid::NAMESPACE_DNS;
    let name = "example.com";
    let id = Uuid::new_v3(namespace, name);
    println!("v3: {}", id);
    
    // Same inputs = same UUID
    let id2 = Uuid::new_v3(namespace, name);
    assert_eq!(id, id2);
}
 
// With v5 feature enabled:
// [dependencies]
// uuid = { version = "1", features = ["v5"] }
 
fn v5_example() {
    use uuid::Uuid;
    
    // v5 - SHA-1 hash based (preferred over v3)
    let namespace = Uuid::NAMESPACE_DNS;
    let name = "example.com";
    let id = Uuid::new_v5(namespace, name);
    println!("v5: {}", id);
    
    // Deterministic - same inputs always produce same UUID
    let id2 = Uuid::new_v5(namespace, name);
    assert_eq!(id, id2);
    
    // Different name = different UUID
    let other = Uuid::new_v5(namespace, "different.com");
    assert_ne!(id, other);
}
 
// With v7 feature enabled:
// [dependencies]
// uuid = { version = "1", features = ["v7"] }
 
fn v7_example() {
    use uuid::Uuid;
    
    // v7 - Time-ordered UUID (newer standard)
    // Combines timestamp with random bits
    let id = Uuid::now_v7();
    println!("v7: {}", id);
    
    // v7 UUIDs are sortable by time
    let id1 = Uuid::now_v7();
    std::thread::sleep(std::time::Duration::from_millis(1));
    let id2 = Uuid::now_v7();
    
    // id2 was created after id1
    println!("id1: {}", id1);
    println!("id2: {}", id2);
}

Parsing UUIDs

use uuid::Uuid;
 
fn main() {
    // Parse from standard hyphenated format
    let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
    println!("Parsed: {}", id);
    
    // Parse from simple format (no hyphens)
    let simple = Uuid::parse_str("550e8400e29b41d4a716446655440000").unwrap();
    println!("Simple: {}", simple);
    
    // Both represent the same UUID
    assert_eq!(id, simple);
    
    // Parse with braces
    let braced = Uuid::parse_str("{550e8400-e29b-41d4-a716-446655440000}").unwrap();
    println!("Braced: {}", braced);
    
    // Parse URN format
    let urn = Uuid::parse_str("urn:uuid:550e8400-e29b-41d4-a716-446655440000").unwrap();
    println!("URN: {}", urn);
    
    // Invalid UUID
    let result = Uuid::parse_str("not-a-uuid");
    println!("Invalid: {:?}", result);
    
    // Parse with error handling
    match Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000") {
        Ok(uuid) => println!("Valid: {}", uuid),
        Err(e) => println!("Error: {}", e),
    }
}
 
// TryParse trait for cleaner error handling
fn try_parse_example() {
    use uuid::Uuid;
    use std::str::FromStr;
    
    // Using FromStr trait
    let id: Uuid = "550e8400-e29b-41d4-a716-446655440000".parse().unwrap();
    println!("FromStr: {}", id);
    
    // Or explicitly
    let id = Uuid::from_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
    println!("from_str: {}", id);
}

UUID Formatting

use uuid::Uuid;
 
fn main() {
    let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
    
    // Default Display (hyphenated)
    println!("Display: {}", id);
    
    // Hyphenated format
    println!("Hyphenated: {}", id.hyphenated());
    
    // Simple format (no hyphens)
    println!("Simple: {}", id.simple());
    
    // URN format
    println!("URN: {}", id.urn());
    
    // Braced format
    println!("Braced: {}", id.braced());
    
    // Get bytes
    let bytes = id.as_bytes();
    println!("Bytes: {:?}", bytes);
    println!("Byte length: {}", bytes.len());
    
    // Convert to various formats
    let hyphenated: String = id.hyphenated().to_string();
    let simple: String = id.simple().to_string();
    let urn: String = id.urn().to_string();
    
    println!("Strings: {} | {} | {}", hyphenated, simple, urn);
    
    // Upper case
    println!("Upper: {}", id.simple().to_string().to_uppercase());
}
 
// Encoding examples
fn encoding_example() {
    use uuid::Uuid;
    
    let id = Uuid::new_v4();
    
    // Get as byte array
    let bytes: &[u8; 16] = id.as_bytes();
    println!("Bytes: {:02x?}", bytes);
    
    // Get as fields (time_low, time_mid, time_hi_and_version, etc.)
    let (time_low, time_mid, time_hi_and_version, clock_seq_and_node) = id.as_fields();
    println!("Fields: {:08x}-{:04x}-{:04x}-...", time_low, time_mid, time_hi_and_version);
    
    // Convert to u128
    let num: u128 = id.as_u128();
    println!("u128: {}", num);
    
    // Convert to [u8; 16]
    let arr: [u8; 16] = *id.as_bytes();
    println!("Array: {:02x?}", arr);
}

Creating UUIDs from Values

use uuid::Uuid;
 
fn main() {
    // From bytes
    let bytes: [u8; 16] = [
        0x55, 0x0e, 0x84, 0x00,
        0xe2, 0x9b,
        0x41, 0xd4,
        0xa7, 0x16,
        0x44, 0x66, 0x55, 0x44, 0x00, 0x00,
    ];
    let id = Uuid::from_bytes(bytes);
    println!("From bytes: {}", id);
    
    // From bytes ref
    let id = Uuid::from_bytes_ref(&bytes);
    println!("From bytes ref: {}", id);
    
    // From u128
    let num: u128 = 0x550e8400_e29b_41d4_a716_446655440000;
    let id = Uuid::from_u128(num);
    println!("From u128: {}", id);
    
    // From fields
    let id = Uuid::from_fields(
        0x550e8400,  // time_low
        0xe29b,      // time_mid
        0x41d4,      // time_hi_and_version
        &[0xa7, 0x16, 0x44, 0x66, 0x55, 0x44, 0x00, 0x00], // clock_seq_and_node
    );
    println!("From fields: {}", id);
    
    // From u64 pair
    let id = Uuid::from_u64_pair(0x550e8400e29b41d4, 0xa716446655440000);
    println!("From u64 pair: {}", id);
}

UUID Properties

use uuid::Uuid;
 
fn main() {
    let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
    
    // Check if nil (all zeros)
    println!("Is nil: {}", id.is_nil());
    
    // Check version
    println!("Version: {:?}", id.get_version());
    
    // Check version number
    if let Some(version) = id.get_version() {
        println!("Version: {:?}", version);
    }
    
    // Check variant
    println!("Variant: {:?}", id.get_variant());
    
    // Get version number directly
    let version_num = id.get_version_num();
    println!("Version number: {}", version_num);
    
    // Check if valid (proper version/variant)
    println!("Valid: {}", id.get_version().is_some());
    
    // Nil UUID
    let nil = Uuid::nil();
    println!("nil is nil: {}", nil.is_nil());
    
    // Max UUID
    let max = Uuid::max();
    println!("max: {}", max);
}
 
// Version info
fn version_info() {
    use uuid::{Uuid, Version};
    
    let v4 = Uuid::new_v4();
    println!("v4 version: {:?}", v4.get_version());
    
    // Parse known versions
    let v1_str = "d9428888-122b-11e1-b85c-61cd3cbb3210";  // v1
    let v4_str = "d9428888-122b-11e1-b85c-61cd3cbb3210".replace("11e1", "41e1");
    
    let v1 = Uuid::parse_str(v1_str).unwrap();
    println!("v1 version: {:?}", v1.get_version());
}

Comparing and Sorting UUIDs

use uuid::Uuid;
use std::collections::{HashMap, HashSet, BTreeSet};
 
fn main() {
    let id1 = Uuid::new_v4();
    let id2 = Uuid::new_v4();
    let id3 = Uuid::new_v4();
    
    // Equality
    println!("id1 == id1: {}", id1 == id1);
    println!("id1 == id2: {}", id1 == id2);
    
    // Ordering
    println!("id1 < id2: {}", id1 < id2);
    println!("id1 < id3: {}", id1 < id3);
    
    // Hash - use in HashMap/HashSet
    let mut set = HashSet::new();
    set.insert(id1);
    set.insert(id2);
    set.insert(id1);  // Duplicate, won't be added
    println!("Set size: {}", set.len());  // 2
    
    // HashMap
    let mut map = HashMap::new();
    map.insert(id1, "first");
    map.insert(id2, "second");
    println!("map[id1]: {:?}", map.get(&id1));
    
    // BTreeSet - sorted order
    let mut sorted = BTreeSet::new();
    sorted.insert(id3);
    sorted.insert(id1);
    sorted.insert(id2);
    
    println!("Sorted:");
    for id in &sorted {
        println!("  {}", id);
    }
}
 
// v7 UUIDs maintain time order
fn v7_sorting() {
    // Requires v7 feature
    // let id1 = Uuid::now_v7();
    // std::thread::sleep(std::time::Duration::from_millis(1));
    // let id2 = Uuid::now_v7();
    // std::thread::sleep(std::time::Duration::from_millis(1));
    // let id3 = Uuid::now_v7();
    
    // v7 UUIDs sort chronologically
    // assert!(id1 < id2);
    // assert!(id2 < id3);
}

Serde Integration

# Cargo.toml
[dependencies]
uuid = { version = "1", features = ["v4", "serde"] }
serde = { version = "1", features = ["derive"] }
use uuid::Uuid;
use serde::{Deserialize, Serialize};
 
#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: Uuid,
    name: String,
    email: String,
}
 
fn main() {
    let user = User {
        id: Uuid::new_v4(),
        name: "Alice".to_string(),
        email: "alice@example.com".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);
    
    // UUIDs serialize as strings by default
    // {"id":"550e8400-e29b-41d4-a716-446655440000","name":"Alice","email":"alice@example.com"}
}
 
// Different serialization formats
fn serialization_formats() {
    use uuid::Uuid;
    use serde_json;
    
    let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
    
    // Default (hyphenated string)
    let json = serde_json::to_string(&id).unwrap();
    println!("Default: {}", json);  // "550e8400-..."
}

UUID Namespaces

use uuid::Uuid;
 
fn main() {
    // Pre-defined namespaces for v3/v5
    
    // DNS namespace
    let dns_ns = Uuid::NAMESPACE_DNS;
    println!("DNS namespace: {}", dns_ns);
    
    // URL namespace
    let url_ns = Uuid::NAMESPACE_URL;
    println!("URL namespace: {}", url_ns);
    
    // OID namespace (ISO OID)
    let oid_ns = Uuid::NAMESPACE_OID;
    println!("OID namespace: {}", oid_ns);
    
    // X.500 DN namespace
    let x500_ns = Uuid::NAMESPACE_X500;
    println!("X500 namespace: {}", x500_ns);
}
 
// Creating deterministic UUIDs with namespaces
// Requires v5 feature
fn deterministic_uuids() {
    use uuid::Uuid;
    
    let namespace = Uuid::NAMESPACE_DNS;
    
    // Same namespace + name = same UUID
    let id1 = Uuid::new_v5(namespace, "user@example.com");
    let id2 = Uuid::new_v5(namespace, "user@example.com");
    assert_eq!(id1, id2);
    
    let id3 = Uuid::new_v5(namespace, "different@example.com");
    assert_ne!(id1, id3);
    
    // Useful for generating consistent IDs from email addresses, URLs, etc.
    println!("Email UUID: {}", id1);
    println!("Different email UUID: {}", id3);
    
    // URL namespace
    let url_ns = Uuid::NAMESPACE_URL;
    let url_id = Uuid::new_v5(url_ns, "https://example.com/page");
    println!("URL UUID: {}", url_id);
}

Builder Pattern

use uuid::{Uuid, Builder};
 
fn main() {
    // Build UUID from components
    let id = Builder::from_bytes([
        0x55, 0x0e, 0x84, 0x00,
        0xe2, 0x9b,
        0x41, 0xd4,
        0xa7, 0x16,
        0x44, 0x66, 0x55, 0x44, 0x00, 0x00,
    ])
    .build();
    
    println!("Built: {}", id);
    
    // Set version and variant
    let id = Builder::from_bytes([0; 16])
        .set_version(uuid::Version::Random)
        .set_variant(uuid::Variant::RFC4122)
        .build();
    
    println!("With version/variant: {}", id);
}

Converting Between Formats

use uuid::Uuid;
 
fn main() {
    let id = Uuid::new_v4();
    
    // To String
    let s = id.to_string();
    println!("String: {}", s);
    
    // To hyphenated string (same as Display)
    let hyphenated = id.hyphenated().to_string();
    println!("Hyphenated: {}", hyphenated);
    
    // To simple string (no hyphens)
    let simple = id.simple().to_string();
    println!("Simple: {}", simple);
    
    // To URN
    let urn = id.urn().to_string();
    println!("URN: {}", urn);
    
    // To bytes
    let bytes: &[u8; 16] = id.as_bytes();
    println!("Bytes: {:02x?}", bytes);
    
    // To u128
    let num: u128 = id.as_u128();
    println!("u128: {}", num);
    
    // To hex string
    let hex = hex::encode(id.as_bytes());
    println!("Hex: {}", hex);
    
    // From various formats back to UUID
    let from_str: Uuid = s.parse().unwrap();
    let from_simple = Uuid::parse_str(&simple).unwrap();
    let from_bytes = Uuid::from_bytes(*bytes);
    let from_u128 = Uuid::from_u128(num);
    
    assert_eq!(id, from_str);
    assert_eq!(id, from_simple);
    assert_eq!(id, from_bytes);
    assert_eq!(id, from_u128);
}
 
// Round-trip conversion
fn round_trip() {
    let original = Uuid::new_v4();
    
    // Through string
    let s = original.to_string();
    let parsed = Uuid::parse_str(&s).unwrap();
    assert_eq!(original, parsed);
    
    // Through bytes
    let bytes = *original.as_bytes();
    let from_bytes = Uuid::from_bytes(bytes);
    assert_eq!(original, from_bytes);
    
    // Through u128
    let num = original.as_u128();
    let from_u128 = Uuid::from_u128(num);
    assert_eq!(original, from_u128);
}

Practical Examples

use uuid::Uuid;
 
// Generate unique IDs for entities
#[derive(Debug, Clone)]
struct Entity {
    id: Uuid,
    name: String,
}
 
impl Entity {
    fn new(name: String) -> Self {
        Self {
            id: Uuid::new_v4(),
            name,
        }
    }
}
 
// Use as database primary key
#[derive(Debug)]
struct Record {
    id: Uuid,
    data: String,
    created_at: String,
}
 
impl Record {
    fn new(data: String) -> Self {
        Self {
            id: Uuid::new_v4(),
            data,
            created_at: chrono::Utc::now().to_rfc3339(),
        }
    }
}
 
fn main() {
    // Create entities with unique IDs
    let e1 = Entity::new("First".to_string());
    let e2 = Entity::new("Second".to_string());
    
    println!("Entity 1: {} -> {}", e1.id, e1.name);
    println!("Entity 2: {} -> {}", e2.id, e2.name);
    
    // Store records
    let records = vec![
        Record::new("Data 1".to_string()),
        Record::new("Data 2".to_string()),
        Record::new("Data 3".to_string()),
    ];
    
    for record in &records {
        println!("Record {}: {}", record.id, record.data);
    }
}
 
// Correlation IDs for distributed tracing
struct RequestContext {
    request_id: Uuid,
    trace_id: Uuid,
    span_id: Uuid,
}
 
impl RequestContext {
    fn new() -> Self {
        Self {
            request_id: Uuid::new_v4(),
            trace_id: Uuid::new_v4(),
            span_id: Uuid::new_v4(),
        }
    }
}
 
// Session tokens
struct Session {
    id: Uuid,
    user_id: Uuid,
    expires_at: std::time::Instant,
}
 
impl Session {
    fn new(user_id: Uuid, duration: std::time::Duration) -> Self {
        Self {
            id: Uuid::new_v4(),
            user_id,
            expires_at: std::time::Instant::now() + duration,
        }
    }
    
    fn is_valid(&self) -> bool {
        std::time::Instant::now() < self.expires_at
    }
}

Database Integration

use uuid::Uuid;
 
// Typically with SQLx, Diesel, or other ORMs
// Example with pseudo-database code
 
struct Database {
    users: std::collections::HashMap<Uuid, User>,
}
 
#[derive(Debug, Clone)]
struct User {
    id: Uuid,
    username: String,
    email: String,
}
 
impl Database {
    fn new() -> Self {
        Self {
            users: std::collections::HashMap::new(),
        }
    }
    
    fn create_user(&mut self, username: String, email: String) -> Uuid {
        let id = Uuid::new_v4();
        let user = User {
            id,
            username,
            email,
        };
        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) -> bool {
        self.users.remove(&id).is_some()
    }
    
    fn find_by_email(&self, email: &str) -> Option<&User> {
        self.users.values().find(|u| u.email == email)
    }
}
 
fn database_example() {
    let mut db = Database::new();
    
    // Create users
    let id1 = db.create_user("alice".to_string(), "alice@example.com".to_string());
    let id2 = db.create_user("bob".to_string(), "bob@example.com".to_string());
    
    println!("Created users: {} and {}", id1, id2);
    
    // Retrieve user
    if let Some(user) = db.get_user(id1) {
        println!("Found: {:?}", user);
    }
    
    // Find by email
    if let Some(user) = db.find_by_email("bob@example.com") {
        println!("Found by email: {:?}", user);
    }
    
    // Delete user
    db.delete_user(id1);
    println!("Users after delete: {}", db.users.len());
}
 
// With SQLx (pseudo-code)
// Requires: uuid = { version = "1", features = ["v4"] }
//
// async fn create_user(pool: &sqlx::PgPool, username: &str) -> Result<Uuid, sqlx::Error> {
//     let id = Uuid::new_v4();
//     sqlx::query!(
//         "INSERT INTO users (id, username) VALUES ($1, $2)",
//         id,
//         username
//     )
//     .execute(pool)
//     .await?;
//     Ok(id)
// }

Testing with UUIDs

use uuid::Uuid;
 
fn main() {
    // In tests, you might want deterministic UUIDs
    
    // Create UUIDs from fixed strings
    let test_id = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
    println!("Test ID: {}", test_id);
    
    // Or use nil as placeholder
    let placeholder = Uuid::nil();
    
    // Helper for tests
    fn test_uuid(n: u8) -> Uuid {
        let bytes = [
            0, 0, 0, 0,
            0, 0,
            0, 0,
            0, 0,
            0, 0, 0, 0, 0, n,
        ];
        Uuid::from_bytes(bytes)
    }
    
    let id1 = test_uuid(1);
    let id2 = test_uuid(2);
    println!("Test IDs: {}, {}", id1, id2);
}
 
#[cfg(test)]
mod tests {
    use super::*;
    use uuid::Uuid;
    
    fn make_test_user(id: &str) -> User {
        User {
            id: Uuid::parse_str(id).unwrap(),
            username: "test".to_string(),
            email: "test@example.com".to_string(),
        }
    }
    
    #[test]
    fn test_user_creation() {
        let user = make_test_user("550e8400-e29b-41d4-a716-446655440000");
        assert_eq!(
            user.id,
            Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap()
        );
    }
}

Summary

  • Use Uuid::new_v4() for random UUIDs (most common)
  • Use Uuid::new_v5(namespace, name) for deterministic, hash-based UUIDs
  • Use Uuid::now_v7() for time-ordered UUIDs (newer, sortable)
  • Enable features in Cargo.toml: features = ["v4", "v5", "v7", "serde"]
  • Parse UUIDs with Uuid::parse_str() or .parse()
  • Format with .hyphenated(), .simple(), .urn(), .braced()
  • Access bytes with .as_bytes() for a &[u8; 16]
  • Convert to/from u128 with .as_u128() and Uuid::from_u128()
  • Check properties with .is_nil(), .get_version(), .get_variant()
  • Use pre-defined namespaces: NAMESPACE_DNS, NAMESPACE_URL, etc.
  • UUIDs implement Copy, Clone, Eq, Hash, Ord for collection use
  • Enable serde feature for JSON serialization
  • Use deterministic UUIDs in tests for reproducibility
  • v4 = random, v5 = SHA-1 hash, v7 = time-ordered (choose based on use case)