uuid Crate (Rust): v4 vs v7 & Serde/SQLx Guide

Max WellsMax WellsFounder of Rustify

TL;DR: The uuid crate provides the Uuid type for generating and parsing UUIDs. Use Uuid::new_v4() for random UUIDs (the standard choice) or Uuid::now_v7() for time-ordered UUIDs (better for database index performance). Enable the serde feature for JSON serialization and the v4 feature for generation. The Uuid type integrates directly with sqlx for UUID database columns.


How Do You Generate a UUID?

Enable the right features in Cargo.toml, then call Uuid::new_v4() for random or Uuid::now_v7() for time-ordered.

[dependencies]
uuid = { version = "1", features = ["v4", "v7", "serde"] }
use uuid::Uuid;
 
fn main() {
    // UUID v4; random, most common
    let id = Uuid::new_v4();
    println!("{id}"); // e.g. 550e8400-e29b-41d4-a716-446655440000
 
    // UUID v7; time-ordered (better for DB indexes)
    let id_v7 = Uuid::now_v7();
    println!("{id_v7}");
 
    // Different string formats
    println!("{}", id.hyphenated()); // 550e8400-e29b-41d4-a716-446655440000
    println!("{}", id.simple());     // 550e8400e29b41d4a716446655440000
    println!("{}", id.urn());        // urn:uuid:550e8400-e29b-41d4-a716-446655440000
 
    // As bytes
    let bytes: [u8; 16] = *id.as_bytes();
}

How Do You Parse and Validate UUIDs?

Use Uuid::parse_str() or the FromStr trait; both return Result and validate the format.

use uuid::Uuid;
use std::str::FromStr;
 
fn main() {
    // Parse from standard hyphenated format
    let id = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
 
    // From string trait
    let id2: Uuid = "550e8400-e29b-41d4-a716-446655440000".parse().unwrap();
 
    // Handle invalid input gracefully
    match Uuid::parse_str("not-a-uuid") {
        Ok(id) => println!("valid: {id}"),
        Err(e) => println!("invalid: {e}"),
    }
 
    // Nil UUID (all zeros)
    let nil = Uuid::nil();
    println!("is nil: {}", nil.is_nil()); // true
}

How Does uuid Work With serde?

Enable the serde feature; Uuid will serialize as a hyphenated string in JSON and deserialize from any valid UUID format.

use serde::{Deserialize, Serialize};
use uuid::Uuid;
 
#[derive(Debug, Serialize, Deserialize)]
struct User {
    id: Uuid,
    name: String,
}
 
fn main() {
    let user = User {
        id: Uuid::new_v4(),
        name: "Alice".to_string(),
    };
 
    let json = serde_json::to_string(&user).unwrap();
    println!("{json}");
    // {"id":"550e8400-e29b-41d4-a716-446655440000","name":"Alice"}
 
    let back: User = serde_json::from_str(&json).unwrap();
    println!("{back:?}");
}

How Does uuid Work With sqlx?

Enable sqlx's uuid feature; Uuid maps directly to PostgreSQL's UUID column type.

[dependencies]
sqlx = { version = "0.8", features = ["postgres", "uuid"] }
uuid = { version = "1", features = ["v4"] }
use sqlx::PgPool;
use uuid::Uuid;
 
struct User {
    id: Uuid,
    name: String,
}
 
async fn get_user(pool: &PgPool, id: Uuid) -> sqlx::Result<Option<User>> {
    sqlx::query_as!(
        User,
        "SELECT id, name FROM users WHERE id = $1",
        id
    )
    .fetch_optional(pool)
    .await
}
 
async fn create_user(pool: &PgPool, name: &str) -> sqlx::Result<Uuid> {
    let id = Uuid::new_v4();
    sqlx::query_scalar!(
        "INSERT INTO users (id, name) VALUES ($1, $2) RETURNING id",
        id,
        name
    )
    .fetch_one(pool)
    .await
}

UUID v4 vs v7; Which Should You Use?

v4 is the default (fully random). v7 is time-ordered; generates sequential UUIDs that are better for database B-tree index performance.

UUID v4UUID v7
RandomnessFully randomTime prefix + random
Sortable✅ (by creation time)
DB index performancePoor (random inserts)Good (sequential inserts)
PrivacyHigh (no time info)Lower (timestamp embedded)
Availabilityfeatures = ["v4"]features = ["v7"]

For new systems with PostgreSQL: prefer v7. For compatibility with existing systems expecting v4: stay with v4.


Frequently Asked Questions

Both are common. UUIDs work better for distributed systems (no central counter), merging data from multiple sources, and hiding record count from users. Auto-increment integers are simpler and have better sequential index performance (unless using UUID v7).

Yes; Uuid implements Copy, Clone, PartialEq, Eq, Hash, Ord, and Display. It's a 16-byte value stored on the stack.

Uuid::nil() is all zeros: 00000000-0000-0000-0000-000000000000. Often used as a sentinel value or default. uuid.is_nil() checks for it.

Enable Diesel's uuid feature. The uuid::Uuid type maps to Diesel's Uuid SQL type on PostgreSQL. Add diesel = { features = ["uuid"] } and uuid = { features = ["v4"] } to Cargo.toml.


Sources


  • Serde: UUID serializes as a string in JSON via the serde feature
  • SQLx: sqlx maps PostgreSQL UUID columns to uuid::Uuid
  • Diesel: Diesel also supports uuid::Uuid via its uuid feature
  • Rand: UUID v4 generation depends on random number generation

Keep Reading

Ready to Land a $80-120k Rust Job?