How does uuid::Uuid::parse_str handle hyphenated vs non-hyphenated UUID string formats?

uuid::Uuid::parse_str accepts both hyphenated (canonical) and non-hyphenated (simple) UUID string formats, parsing them identically into a 128-bit UUID value while rejecting invalid formats with descriptive errors. The function is format-flexible by design—it strips hyphens during parsing and validates that the remaining characters form a valid hexadecimal representation of exactly 128 bits.

Supported UUID Formats

use uuid::Uuid;
 
fn supported_formats() -> Result<(), uuid::Error> {
    // Hyphenated (canonical) format: 8-4-4-4-12
    let hyphenated = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000")?;
    
    // Non-hyphenated (simple) format: 32 hex characters
    let simple = Uuid::parse_str("550e8400e29b41d4a716446655440000")?;
    
    // Both produce the same UUID
    assert_eq!(hyphenated, simple);
    
    // With braces (Microsoft GUID style)
    let braced = Uuid::parse_str("{550e8400-e29b-41d4-a716-446655440000}")?;
    assert_eq!(braced, hyphenated);
    
    // URN format
    let urn = Uuid::parse_str("urn:uuid:550e8400-e29b-41d4-a716-446655440000")?;
    assert_eq!(urn, hyphenated);
    
    Ok(())
}

parse_str handles multiple common UUID string representations transparently.

The Hyphenated Canonical Format

use uuid::Uuid;
 
fn hyphenated_format() {
    // Canonical UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
    // 8 hex chars - 4 hex chars - 4 hex chars - 4 hex chars - 12 hex chars
    // Total: 32 hex digits + 4 hyphens = 36 characters
    
    let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
    
    // Breaking down the format:
    // 550e8400  - time_low (8 hex = 32 bits)
    // e29b      - time_mid (4 hex = 16 bits)
    // 41d4      - time_hi_and_version (4 hex = 16 bits)
    // a716      - clock_seq_hi_and_reserved + clock_seq_low (4 hex = 16 bits)
    // 446655440000 - node (12 hex = 48 bits)
    // Total: 128 bits
    
    println!("UUID: {}", uuid);
    // Output: 550e8400-e29b-41d4-a716-446655440000
    
    // The hyphens are purely for readability
    // They separate the logical components of the UUID
}

The canonical format uses hyphens at specific positions to improve readability and separate logical fields.

The Non-Hyphenated Simple Format

use uuid::Uuid;
 
fn simple_format() {
    // Simple format: 32 hexadecimal characters, no hyphens
    let uuid = Uuid::parse_str("550e8400e29b41d4a716446655440000").unwrap();
    
    // Same UUID as the hyphenated version
    let hyphenated = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
    assert_eq!(uuid, hyphenated);
    
    // The simple format is just the hex characters concatenated
    // Useful for:
    // - Compact storage in URLs
    // - Database columns (saves 4 bytes vs hyphenated)
    // - File names (no hyphen issues)
    // - Copy-paste scenarios
    
    println!("Simple: {}", uuid.to_string()); // Uses hyphenated by default
    println!("Simple format: {}", uuid.hyphenated()); // Explicit hyphenated
    println!("No hyphens: {}", uuid.simple()); // Explicit simple
}

The simple format is more compact but equivalent in meaning.

Parsing Implementation Details

use uuid::Uuid;
 
fn parsing_internals() {
    // parse_str does the following:
    // 1. Remove optional braces {} or urn:uuid: prefix
    // 2. Remove hyphens from the string
    // 3. Validate remaining characters are hex
    // 4. Ensure exactly 32 hex characters remain
    // 5. Parse the 128 bits
    
    // These all parse to the same UUID:
    let formats = [
        "550e8400-e29b-41d4-a716-446655440000",  // hyphenated
        "550e8400e29b41d4a716446655440000",       // simple
        "{550e8400-e29b-41d4-a716-446655440000}", // braced
        "urn:uuid:550e8400-e29b-41d4-a716-446655440000", // URN
    ];
    
    let expected = Uuid::parse_str("550e8400e29b41d4a716446655440000").unwrap();
    
    for format in formats {
        let parsed = Uuid::parse_str(format).unwrap();
        assert_eq!(parsed, expected);
    }
    
    // The hyphens are stripped during parsing
    // The result is always the same 128-bit UUID
}

Internally, parse_str normalizes all formats to a common representation before decoding.

Error Handling for Invalid Formats

use uuid::Uuid;
 
fn error_handling() {
    // Too few characters
    let result = Uuid::parse_str("550e8400e29b41d4a716");
    assert!(result.is_err());
    println!("Too short: {:?}", result.unwrap_err());
    // Error: invalid length, expected 32 hex characters
    
    // Too many characters
    let result = Uuid::parse_str("550e8400e29b41d4a716446655440000extra");
    assert!(result.is_err());
    
    // Invalid characters
    let result = Uuid::parse_str("550e8400e29b41d4a71644665544000g");
    assert!(result.is_err());
    // Error: invalid character 'g', expected hex
    
    // Wrong hyphen positions
    let result = Uuid::parse_str("550e8400-e29b41d4-a716-446655440000");
    assert!(result.is_err());
    // Hyphens must be at positions 8, 13, 18, 23 (in hyphenated format)
    
    // Empty string
    let result = Uuid::parse_str("");
    assert!(result.is_err());
    
    // Non-hex in simple format
    let result = Uuid::parse_str("550e8400e29b41d4a71644665544000z");
    assert!(result.is_err());
}

parse_str validates format strictly and returns detailed errors.

Hyphen Position Validation

use uuid::Uuid;
 
fn hyphen_positions() {
    // Correct hyphen positions: 8, 13, 18, 23 (0-indexed)
    // 550e8400-e29b-41d4-a716-446655440000
    // 01234567 8 9012 3 4567 8 9012 3 456789012345
    //          ^     ^     ^     ^
    //          hyphen positions
    
    // This is valid
    let valid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000");
    assert!(valid.is_ok());
    
    // Wrong positions - not valid
    let wrong = Uuid::parse_str("550e-8400-e29b-41d4-a716-446655440000");
    assert!(wrong.is_err());
    
    // Multiple hyphens in wrong places
    let wrong2 = Uuid::parse_str("550e8400e-29b41d4a-716446655440000");
    assert!(wrong2.is_err());
    
    // If using hyphens, they MUST be at the right positions
    // Otherwise, use simple format (no hyphens at all)
}

When hyphens are present, they must be at canonical positions; otherwise parsing fails.

Case Insensitivity

use uuid::Uuid;
 
fn case_insensitivity() {
    // Uppercase
    let upper = Uuid::parse_str("550E8400-E29B-41D4-A716-446655440000").unwrap();
    
    // Lowercase
    let lower = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
    
    // Mixed case
    let mixed = Uuid::parse_str("550e8400-E29b-41D4-A716-446655440000").unwrap();
    
    // All produce the same UUID
    assert_eq!(upper, lower);
    assert_eq!(lower, mixed);
    
    // The parser is case-insensitive for hex digits
    // a-f and A-F are treated identically
    
    // But output format can be controlled:
    let uuid = Uuid::parse_str("550e8400e29b41d4a716446655440000").unwrap();
    println!("Default: {}", uuid); // lowercase hyphenated
    println!("Uppercase: {}", uuid.hyphenated().to_string().to_uppercase());
}

Hex digits are case-insensitive during parsing; a-f and A-F are equivalent.

Working with Braced and URN Formats

use uuid::Uuid;
 
fn braced_and_urn() {
    // Braced format (Microsoft GUID style)
    let braced = Uuid::parse_str("{550e8400-e29b-41d4-a716-446655440000}").unwrap();
    
    // URN format
    let urn = Uuid::parse_str("urn:uuid:550e8400-e29b-41d4-a716-446655440000").unwrap();
    
    // Both equivalent to simple format
    let simple = Uuid::parse_str("550e8400e29b41d4a716446655440000").unwrap();
    
    assert_eq!(braced, simple);
    assert_eq!(urn, simple);
    
    // The parser strips:
    // - Leading/trailing braces {}
    // - Leading "urn:uuid:" prefix
    
    // Invalid braces:
    let result = Uuid::parse_str("{550e8400-e29b-41d4-a716-446655440000");
    assert!(result.is_err()); // Missing closing brace
    
    let result = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000}");
    assert!(result.is_err()); // Missing opening brace
    
    // Braces must be paired
}

Braces and URN prefixes are stripped during parsing if present.

Output Format Control

use uuid::Uuid;
 
fn output_formats() {
    let uuid = Uuid::parse_str("550e8400e29b41d4a716446655440000").unwrap();
    
    // Default Display is hyphenated
    println!("Display: {}", uuid);
    // Output: 550e8400-e29b-41d4-a716-446655440000
    
    // Explicit hyphenated
    println!("Hyphenated: {}", uuid.hyphenated());
    // Output: 550e8400-e29b-41d4-a716-446655440000
    
    // Simple (no hyphens)
    println!("Simple: {}", uuid.simple());
    // Output: 550e8400e29b41d4a716446655440000
    
    // URN format
    println!("URN: {}", uuid.urn());
    // Output: urn:uuid:550e8400-e29b-41d4-a716-446655440000
    
    // Braced format
    println!("Braced: {}", uuid.braced());
    // Output: {550e8400-e29b-41d4-a716-446655440000}
    
    // For URLs, simple format is often preferred
    let url = format!("https://example.com/items/{}", uuid.simple());
    println!("URL: {}", url);
    // Output: https://example.com/items/550e8400e29b41d4a716446655440000
}

The Uuid type provides methods to format output in various styles.

Practical Parsing Patterns

use uuid::Uuid;
use std::str::FromStr;
 
fn parsing_patterns() {
    // Method 1: parse_str (returns Result)
    let uuid1: Result<Uuid, uuid::Error> = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000");
    
    // Method 2: parse (via FromStr trait)
    let uuid2: Result<Uuid, uuid::Error> = "550e8400-e29b-41d4-a716-446655440000".parse();
    
    // Method 3: Using FromStr trait
    let uuid3 = Uuid::from_str("550e8400-e29b-41d4-a716-446655440000");
    
    // All produce the same result
    assert_eq!(uuid1.unwrap(), uuid2.unwrap());
    assert_eq!(uuid2.unwrap(), uuid3.unwrap());
    
    // Choose based on context:
    // - parse_str: Explicit, clear intent
    // - .parse(): Idiomatic Rust, works with generic code
    // - FromStr: When trait is required
}
 
fn user_input_handling() {
    // User input might have various formats
    fn parse_user_uuid(input: &str) -> Result<Uuid, String> {
        // Trim whitespace
        let trimmed = input.trim();
        
        // parse_str handles all valid formats
        Uuid::parse_str(trimmed).map_err(|e| format!("Invalid UUID: {}", e))
    }
    
    // All these work:
    assert!(parse_user_uuid("550e8400-e29b-41d4-a716-446655440000").is_ok());
    assert!(parse_user_uuid("550e8400e29b41d4a716446655440000").is_ok());
    assert!(parse_user_uuid("{550e8400-e29b-41d4-a716-446655440000}").is_ok());
    assert!(parse_user_uuid("  550e8400-e29b-41d4-a716-446655440000  ").is_ok());
}

Multiple parsing APIs exist; choose based on clarity and context.

Database and Storage Considerations

use uuid::Uuid;
 
fn storage_formats() {
    let uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
    
    // For database storage, consider:
    
    // 1. Binary storage (16 bytes) - most efficient
    let bytes = uuid.as_bytes();
    println!("Binary size: {} bytes", bytes.len()); // 16 bytes
    
    // 2. Hyphenated string (36 characters)
    let hyphenated = uuid.hyphenated().to_string();
    println!("Hyphenated size: {} bytes", hyphenated.len()); // 36 bytes
    
    // 3. Simple string (32 characters)
    let simple = uuid.simple().to_string();
    println!("Simple size: {} bytes", simple.len()); // 32 bytes
    
    // Space comparison for storing 1 million UUIDs:
    // Binary: 16 MB
    // Hyphenated: 36 MB
    // Simple: 32 MB
    
    // Recommendation:
    // - Use binary for internal storage (most efficient)
    // - Use hyphenated for human-readable display
    // - Use simple for URLs (no special characters)
}
 
fn database_interop() {
    // PostgreSQL supports UUID type natively
    // Can accept hyphenated or simple format
    let uuid = Uuid::parse_str("550e8400e29b41d4a716446655440000").unwrap();
    
    // For query parameters, use simple format
    let query = format!("SELECT * FROM items WHERE id = '{}'", uuid.simple());
    
    // Or use hyphenated (PostgreSQL accepts both)
    let query2 = format!("SELECT * FROM items WHERE id = '{}'", uuid.hyphenated());
    
    // MySQL doesn't have native UUID type
    // Store as BINARY(16) or CHAR(36)
    // CHAR(36) for hyphenated, CHAR(32) for simple
}

Format choice affects storage size and database compatibility.

Parsing Error Types

use uuid::Uuid;
 
fn error_details() {
    // uuid::Error provides detailed error information
    let result = Uuid::parse_str("invalid");
    
    match result {
        Ok(uuid) => println!("Parsed: {}", uuid),
        Err(e) => {
            // Error implements Display with useful message
            println!("Error: {}", e);
            
            // Common error cases:
            // - "invalid length: expected 32 hex characters"
            // - "invalid character: expected hex"
            // - "invalid hyphen position"
            // - "invalid group count: expected 5 groups"
        }
    }
    
    // Example error scenarios:
    
    // Length errors
    let err1 = Uuid::parse_str("550e8400"); // Too short
    println!("Length error: {}", err1.unwrap_err());
    
    // Character errors
    let err2 = Uuid::parse_str("550e8400z29b41d4a716446655440000"); // Invalid char
    println!("Character error: {}", err2.unwrap_err());
    
    // Hyphen position errors
    let err3 = Uuid::parse_str("550e-8400e29b-41d4-a716-446655440000"); // Wrong position
    println!("Position error: {}", err3.unwrap_err());
}

Errors are descriptive, helping users correct format issues.

Round-Trip Consistency

use uuid::Uuid;
 
fn round_trip() {
    // Creating a UUID and converting to string
    let original = Uuid::new_v4();
    
    // Hyphenated round trip
    let hyphenated = original.hyphenated().to_string();
    let parsed_hyphen = Uuid::parse_str(&hyphenated).unwrap();
    assert_eq!(original, parsed_hyphen);
    
    // Simple round trip
    let simple = original.simple().to_string();
    let parsed_simple = Uuid::parse_str(&simple).unwrap();
    assert_eq!(original, parsed_simple);
    
    // URN round trip
    let urn = original.urn().to_string();
    let parsed_urn = Uuid::parse_str(&urn).unwrap();
    assert_eq!(original, parsed_urn);
    
    // Braced round trip
    let braced = original.braced().to_string();
    let parsed_braced = Uuid::parse_str(&braced).unwrap();
    assert_eq!(original, parsed_braced);
    
    // All formats round-trip correctly
}

All output formats can be parsed back to the original UUID.

Summary Table

fn summary() {
    // | Format      | Example                                          | Length | Notes           |
    // |-------------|--------------------------------------------------|--------|-----------------|
    // | Hyphenated  | 550e8400-e29b-41d4-a716-446655440000            | 36     | Canonical       |
    // | Simple      | 550e8400e29b41d4a716446655440000                 | 32     | Compact         |
    // | Braced      | {550e8400-e29b-41d4-a716-446655440000}           | 38     | MS GUID         |
    // | URN         | urn:uuid:550e8400-e29b-41d4-a716-446655440000    | 45     | RFC 4122        |
    
    // | Method      | Output                                           | Use Case        |
    // |-------------|--------------------------------------------------|-----------------|
    // | hyphenated()| 550e8400-e29b-41d4-a716-446655440000            | Human-readable  |
    // | simple()    | 550e8400e29b41d4a716446655440000                 | URLs, storage   |
    // | urn()       | urn:uuid:550e8400...                             | RDF, semantic   |
    // | braced()    | {550e8400-e29b-41d4-...}                         | MS interop     |
    
    // | Storage     | Size for 1M UUIDs | Recommendation            |
    // |-------------|-------------------|---------------------------|
    // | Binary(16)  | 16 MB             | Most efficient            |
    // | Simple      | 32 MB             | Compact string             |
    // | Hyphenated  | 36 MB             | Human-readable string     |
}

Synthesis

Quick reference:

use uuid::Uuid;
 
// Parsing accepts all common formats
let hyphenated = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000")?;
let simple = Uuid::parse_str("550e8400e29b41d4a716446655440000")?;
let braced = Uuid::parse_str("{550e8400-e29b-41d4-a716-446655440000}")?;
let urn = Uuid::parse_str("urn:uuid:550e8400-e29b-41d4-a716-446655440000")?;
 
// All produce the same UUID
assert_eq!(hyphenated, simple);
assert_eq!(simple, braced);
assert_eq!(braced, urn);
 
// Output in your preferred format
let uuid = Uuid::new_v4();
println!("{}", uuid.hyphenated()); // Canonical format
println!("{}", uuid.simple());     // Compact format

Key insight: Uuid::parse_str is deliberately format-flexible because UUIDs appear in various textual representations across systems—hyphenated canonical form from standards, simple form from databases, braced form from Microsoft GUIDs, and URN form from RDF/semantic web contexts. The parser normalizes all these by stripping decorations (braces, URN prefix, hyphens) and validating that exactly 32 hexadecimal characters remain, representing 128 bits. This design allows applications to accept UUIDs in any common format without preprocessing, while still validating strictly—wrong hyphen positions, non-hex characters, and incorrect lengths all produce clear errors. The trade-off is parsing overhead from format detection and string manipulation, but this is negligible compared to typical I/O operations. For output, the Uuid type provides explicit methods (hyphenated(), simple(), urn(), braced()) rather than guessing which format the caller wants, ensuring round-trip consistency and explicit intent. For storage efficiency, prefer binary storage (16 bytes) or the simple format (32 characters) over hyphenated (36 characters), using hyphenated primarily for human-readable display.