Generating UUIDs in Rust
On this page
The uuid crate is the universal Rust UUID library — well-maintained, no-std friendly, with features gated behind flags so you only pay for what you use.
Setup
cargo add uuid --features v4,v7,serde
Each feature flag enables a specific version or capability:
| Feature | Enables |
|---|---|
v1 | Time-based (timestamp + node) |
v3 | Name-based (MD5) |
v4 | Random — most common |
v5 | Name-based (SHA-1) |
v6 | v1 with sortable timestamp |
v7 | Unix-ms timestamp + random |
v8 | Custom |
serde | serde Serialize/Deserialize |
fast-rng | Use a faster (non-cryptographic) RNG |
js | WASM/JS environments |
Quick start
use uuid::Uuid;
fn main() {
let v4 = Uuid::new_v4();
let v7 = Uuid::now_v7();
let v5 = Uuid::new_v5(&Uuid::NAMESPACE_URL, b"https://example.com");
println!("{}", v4); // canonical form
println!("{}", v4.simple()); // no hyphens
println!("{}", v4.braced()); // {with-braces}
println!("{}", v4.urn()); // urn:uuid:...
}
Uuid is Copy — a 16-byte value you pass around freely without
borrowing. The various format methods return temporary structs that
implement Display, so they’re zero-allocation when used with
format! / write!.
Parsing
let s = "0e6f1b8c-2c33-4f1f-9c0b-2a3d4e5f6a7b";
let id: Uuid = s.parse()?; // canonical
let id = Uuid::parse_str(s)?; // explicit
parse_str accepts canonical, no-hyphens, braced, and URN forms. Invalid
input returns uuid::Error.
Serde / JSON
use uuid::Uuid;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct User {
id: Uuid,
email: String,
}
let u = User { id: Uuid::now_v7(), email: "alice@example.com".into() };
let json = serde_json::to_string(&u)?;
// {"id":"01928a47-3b30-7c5e-9d1a-f0b8c4a7e923","email":"alice@example.com"}
UUIDs serialize to their canonical string by default.
sqlx integration
[dependencies]
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "uuid"] }
uuid = { version = "1", features = ["v7", "serde"] }
use sqlx::{PgPool, Row};
use uuid::Uuid;
#[derive(sqlx::FromRow)]
struct User {
id: Uuid,
email: String,
}
let user: User = sqlx::query_as(
"SELECT id, email FROM users WHERE id = $1"
)
.bind(id)
.fetch_one(&pool)
.await?;
let id = Uuid::now_v7();
sqlx::query("INSERT INTO users (id, email) VALUES ($1, $2)")
.bind(id)
.bind(&email)
.execute(&pool)
.await?;
sqlx’s uuid feature enables transparent conversion between PostgreSQL uuid and Rust Uuid.
Diesel integration
[dependencies]
diesel = { version = "2.2", features = ["postgres", "uuid"] }
uuid = { version = "1", features = ["v7"] }
use diesel::prelude::*;
use uuid::Uuid;
#[derive(Insertable, Queryable, Selectable)]
#[diesel(table_name = users)]
struct User {
id: Uuid,
email: String,
}
Bytes and byte order
let id = Uuid::new_v4();
let bytes: &[u8; 16] = id.as_bytes(); // borrowed, RFC 4122 order
let owned: [u8; 16] = id.into_bytes(); // owned
let from_bytes = Uuid::from_bytes(owned);
This is RFC 4122 / network byte order. For Microsoft’s GUID byte order (when reading SQL Server uniqueidentifier raw bytes), see UUID vs GUID.
Const UUIDs (compile-time)
use uuid::uuid;
const ORDERS_NS: Uuid = uuid!("8a4c2d6e-1f3b-4a5c-9d8e-7f6a5b4c3d2e");
The uuid! macro parses at compile time, so an invalid literal becomes a compile error rather than a runtime panic.
WASM / browsers
uuid = { version = "1", features = ["v4", "v7", "js"] }
The js feature pulls in getrandom configured to use the browser’s crypto.getRandomValues(). Without it, you’ll get a runtime error in WASM.
no_std
uuid = { version = "1", default-features = false, features = ["v4"] }
Disable default features and pick what you need. The crate works on no_std for embedded use cases.
Performance
| Approach | Throughput (single-threaded) |
|---|---|
Uuid::new_v4() (default rng) | ~3M/sec |
Uuid::new_v4() with fast-rng | ~12M/sec |
Uuid::now_v7() | ~5M/sec |
Default uses getrandom (cryptographically secure). fast-rng uses rand with a faster (non-secure) PRNG. Don’t use fast-rng for security tokens.
Common pitfalls
Uuid::new_v4()requires thev4feature flag. Forgetting to enable it inCargo.tomlproduces a confusing “method not found” error. Same for v7 / v5 / etc.- Don’t use
fast-rngUUIDs as session tokens. They’re fast because they’re not cryptographic. Use the default RNG for any unguessable token. Uuid::default()is the nil UUID (all zeros). If aUuidfield has the default trait used for initialization, check before using it as a real ID.- WASM targets need the
jsfeature. Without it, random UUID generation panics with “no implementation for this target.”
Cheat sheet
| Goal | Code |
|---|---|
| Random UUID | Uuid::new_v4() |
| Time-ordered (v7) | Uuid::now_v7() |
| Deterministic (v5) | Uuid::new_v5(&ns, b"name") |
| Parse | s.parse::<Uuid>() |
| Const UUID | uuid!("0e6f1b8c-...") |
| Bytes (RFC order) | id.as_bytes() |
| Empty / nil | Uuid::nil() |
Try the tools
- UUID generator — generate v4, v7, v1/v3/v5
- UUID v7 generator — modern primary keys
- UUID validator — decode and check version