Loading pageā¦
Rust walkthroughs
Loading pageā¦
uuid::Uuid::new_v4 and new_v7 for generating unique identifiers?UUID v4 generates fully random identifiers with 122 random bits, providing no ordering guarantees but maximum unpredictability. UUID v7 combines a millisecond-precision Unix timestamp with random bits, producing time-sortable identifiers that dramatically improve database index performance and enable chronological queries. The choice between them centers on whether you need sortability and database efficiency (v7) or maximum randomness for security-sensitive contexts (v4).
use uuid::Uuid;
fn main() {
// UUID v4: 122 random bits (6 bits used for version/variant)
let id1 = Uuid::new_v4();
let id2 = Uuid::new_v4();
let id3 = Uuid::new_v4();
println!("v4 #1: {}", id1);
println!("v4 #2: {}", id2);
println!("v4 #3: {}", id3);
// Example output:
// v4 #1: 550e8400-e29b-41d4-a716-446655440000
// v4 #2: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
// v4 #3: f47ac10b-58cc-4372-a567-0e02b2c3d479
// Note: No ordering relationship between these IDs
// Each is completely independent
// v4 structure:
// - Version bits: 0100 (version 4)
// - Variant bits: 10xx (RFC 4122 variant)
// - Remaining 122 bits: random
}UUID v4 is pure randomnessāno two v4 UUIDs have any predictable relationship.
use uuid::Uuid;
use std::thread;
use std::time::Duration;
fn main() {
// UUID v7: Timestamp prefix + random suffix
let id1 = Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext));
thread::sleep(Duration::from_millis(10));
let id2 = Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext));
thread::sleep(Duration::Duration::from_millis(10));
let id3 = Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext));
println!("v7 #1: {}", id1);
println!("v7 #2: {}", id2);
println!("v7 #3: {}", id3);
// Example output:
// v7 #1: 018f3b6a-1c2d-7d3e-8f4a-5b6c7d8e9f0a
// v7 #2: 018f3b6a-1c37-7e4f-9g5a-6b7c8d9e0f1a
// v7 #3: 018f3b6a-1c41-8f5g-0h6a-7b8c9d0e1f2a
// Note: IDs are roughly ordered by creation time
// The first 48 bits encode the Unix timestamp in milliseconds
// v7 structure:
// - 48 bits: Unix timestamp (milliseconds)
// - 4 bits: Version (0111 = version 7)
// - 12 bits: rand_a (can include sequence/counter)
// - 2 bits: Variant
// - 62 bits: rand_b (random)
}UUID v7 starts with a timestamp, making IDs sortable by creation time.
use uuid::Uuid;
fn main() {
// v4: No ordering
let v4_ids: Vec<Uuid> = (0..5).map(|_| Uuid::new_v4()).collect();
println!("v4 UUIDs (no ordering):");
for id in &v4_ids {
println!(" {}", id);
}
// v7: Ordered by creation time
let v7_ids: Vec<Uuid> = (0..5)
.map(|_| Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext)))
.collect();
println!("\nv7 UUIDs (time-ordered):");
for id in &v7_ids {
println!(" {}", id);
}
// Sorting v4 vs v7:
let mut v4_sorted = v4_ids.clone();
v4_sorted.sort();
let mut v7_sorted = v7_ids.clone();
v7_sorted.sort();
// v4: sorted order is meaningless
// v7: sorted order approximates creation order
println!("\nv7 sorted approximates creation order");
println!("v4 sorted has no relationship to creation order");
}v7 UUIDs sort approximately by creation time; v4 UUIDs sort randomly.
// This is conceptual - actual database interaction depends on your setup
use uuid::Uuid;
fn main() {
// v4 in database indexes:
// - Random insertion order
// - Every insert goes to random B-tree position
// - Causes page splits and fragmentation
// - Poor cache locality
// - Degrades as table grows
// v7 in database indexes:
// - Sequential insertion (roughly)
// - Inserts go to end of B-tree (mostly)
// - Fewer page splits
// - Better cache locality
// - Scales better with table size
// Example: Inserting 1 million UUIDs
// v4 scenario:
// - Index has 1M random positions
// - Each insert may touch any page
// - Many random I/O operations
// v7 scenario:
// - Index grows mostly at the end
// - Inserts cluster in recent pages
// - Sequential I/O where possible
// Performance impact can be 2-10x for large tables
// (Depends on database, hardware, workload)
}v7's sequential nature dramatically improves B-tree index performance compared to v4's random inserts.
use uuid::Uuid;
fn main() {
// v4 collision probability:
// - 122 random bits
// - Collision after ~2^61 IDs (birthday paradox)
// - Approximately: 1 collision per 5 trillion IDs
// v7 collision probability:
// - Depends on implementation
// - Timestamp precision: milliseconds
// - Random bits: 74 bits (12 + 62)
// - Same millisecond: collision after ~2^37 IDs
// Practical collision analysis:
// v4: Global uniqueness guaranteed by randomness
// No coordination needed between systems
// v7: Timestamp prefix + random suffix
// Same timestamp + same random = collision
// Probability: extremely low for reasonable workloads
// Within same millisecond:
// - v4: 2^-122 collision per pair
// - v7: 2^-74 collision per pair (still astronomically low)
// For distributed systems:
// - v4: No coordination needed
// - v7: Clock synchronization matters (but uuid crate handles it)
// uuid crate's v7 implementation:
// - Uses counter/sequence for same-millisecond IDs
// - Further reduces collision risk
println!("Both v4 and v7 have collision probabilities near zero for practical use");
println!("v7: ~74 random bits per millisecond");
println!("v4: 122 random bits total");
}Both are collision-resistant; v4 has slightly more random bits but v7's counter mechanism compensates.
use uuid::Uuid;
fn main() {
let v7 = Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext));
// v7 encodes creation timestamp
if let Some(timestamp) = v7.get_timestamp() {
let (seconds, nanos) = timestamp.to_unix();
println!("Created at: {} seconds since Unix epoch", seconds);
println!("Nanoseconds: {}", nanos);
// Convert to chrono time (if chrono feature enabled)
// let datetime: chrono::DateTime<chrono::Utc> = timestamp.into();
}
// v4 has no extractable timestamp
let v4 = Uuid::new_v4();
assert!(v4.get_timestamp().is_none());
// Use case: Debugging, auditing, filtering by creation time
// SELECT * FROM events WHERE id >= <start_of_day_v7>
// (If IDs are v7, this approximates time filtering)
}v7 allows timestamp extraction; v4 provides no timing information.
use uuid::Uuid;
fn main() {
// v4: Completely unpredictable
// - 122 random bits
// - No information about creation time
// - No information about other IDs from same system
// v7: Partially predictable
// - Timestamp reveals creation time (millisecond precision)
// - Counter reveals approximate ID generation rate
// - Random bits still unpredictable
// Security implications:
// Scenario 1: Session tokens
// - v4: Better choice
// - No timing information leaked
// - Attacker cannot infer when session was created
// Scenario 2: Order IDs (public-facing)
// - v7 reveals order creation time
// - v7 reveals approximate order volume
// - May be acceptable for public order IDs
// Scenario 3: Internal entity IDs
// - v7 acceptable (internal systems)
// - Performance benefits outweigh timing exposure
// Scenario 4: URLs / public resources
// - v7 leaks creation time (may matter)
// - v4 hides everything
// For security-sensitive contexts, prefer v4
// Example: Don't use v7 for password reset tokens
// let reset_token = Uuid::new_v4(); // Correct: unpredictable
// v7 for password reset reveals approximate time of request:
// let reset_token = Uuid::new_v7(...); // Avoid: leaks timing
}v7 leaks timing information; v4 is fully unpredictable. Use v4 for security-sensitive tokens.
use uuid::{Uuid, Timestamp, NoContext};
fn main() {
// Basic v7 usage:
let id1 = Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext));
// NoContext: No counter, pure timestamp + random
// - Simpler implementation
// - Same-millisecond collisions possible (rare)
// For high-volume ID generation within same millisecond:
// uuid crate provides context for monotonic IDs
// The v7 spec allows for different implementations:
// 1. Method 1: Reset random each millisecond
// 2. Method 2: Counter/sequence for same-millisecond IDs
// 3. Method 3: Monotonic random
// uuid crate's implementation:
// - Uses NoContext for basic usage
// - Can use custom context for monotonic generation
// For distributed systems, consider:
// - Clock synchronization
// - Node IDs (can encode in rand_a section)
// - Counter for same-millisecond IDs
// Standard v7 is sufficient for most applications
// Custom implementations for high-throughput distributed systems
println!("v7 ID: {}", id1);
}The uuid crate's v7 handles same-millisecond generation with proper context.
use uuid::Uuid;
// Use v7 for entity IDs in databases
#[derive(Debug)]
struct User {
id: Uuid, // v7
email: String,
created_at: chrono::DateTime<chrono::Utc>,
}
impl User {
fn new(email: String) -> Self {
User {
id: Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext)),
email,
created_at: chrono::Utc::now(),
}
}
}
// Use v4 for security tokens
#[derive(Debug)]
struct Session {
id: Uuid, // v7 for entity
token: Uuid, // v4 for security
user_id: Uuid,
}
impl Session {
fn new(user_id: Uuid) -> Self {
Session {
id: Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext)),
token: Uuid::new_v4(), // Unpredictable
user_id,
}
}
}
// Use v7 for event/log IDs (time-sortable)
#[derive(Debug)]
struct Event {
id: Uuid, // v7
event_type: String,
timestamp: chrono::DateTime<chrono::Utc>,
}
impl Event {
fn new(event_type: String) -> Self {
let id = Uuid::new_v7(uuid::Timestamp::now(uuid::NoContext));
Event {
id,
event_type,
timestamp: chrono::Utc::now(),
}
}
}
fn main() {
let user = User::new("user@example.com".to_string());
let session = Session::new(user.id);
let event = Event::new("user_login".to_string());
println!("User ID (v7): {}", user.id);
println!("Session token (v4): {}", session.token);
println!("Event ID (v7): {}", event.id);
}Use v7 for entity/event IDs, v4 for security tokens and unpredictable identifiers.
use uuid::Uuid;
// v7 enables time-range queries on ID
// Conceptual SQL examples:
// Find events in a time range using v7 IDs:
// SELECT * FROM events
// WHERE id >= <v7_id_at_start> AND id < <v7_id_at_end>
// Since v7 IDs sort by time, this is efficient
// With v4, you'd need a separate timestamp column:
// SELECT * FROM events
// WHERE created_at >= '2024-01-01' AND created_at < '2024-02-01'
// v7 reduces need for separate timestamp index in some cases
// (But still good practice to have explicit timestamps)
// Pagination by ID:
// v4: Random pagination, poor cache locality
// SELECT * FROM items WHERE id > <v4_id> ORDER BY id LIMIT 100
// Results in random access pattern
// v7: Sequential pagination, good cache locality
// SELECT * FROM items WHERE id > <v7_id> ORDER BY id LIMIT 100
// Results in mostly sequential access
// Time-based filtering:
fn filter_by_time_range() {
// Generate v7 IDs for time bounds
let start_time = uuid::Timestamp::from_unix(uuid::NoContext, 1704067200, 0); // 2024-01-01
let end_time = uuid::Timestamp::from_unix(uuid::NoContext, 1706745600, 0); // 2024-02-01
let start_id = Uuid::new_v7(start_time);
let end_id = Uuid::new_v7(end_time);
// Query: SELECT * FROM items WHERE id >= start_id AND id < end_id
println!("Time range query:");
println!(" Start ID: {}", start_id);
println!(" End ID: {}", end_id);
}v7 IDs support time-based queries directly; v4 requires separate timestamp columns.
use uuid::Uuid;
fn main() {
// Summary comparison:
// UUID v4:
// - Randomness: 122 bits
// - Sortability: No
// - Timestamp extraction: No
// - DB index performance: Poor (random inserts)
// - Collision resistance: Excellent
// - Security: Best (no timing info)
// - Use cases: Security tokens, session IDs, public URLs
// UUID v7:
// - Randomness: ~74 bits (per millisecond)
// - Sortability: Yes (time-ordered)
// - Timestamp extraction: Yes
// - DB index performance: Excellent (sequential)
// - Collision resistance: Excellent
// - Security: Good (leaks timing)
// - Use cases: Entity IDs, event IDs, log IDs
// Quick reference:
// Need maximum unpredictability? -> v4
// Need time-sortability? -> v7
// High-volume database inserts? -> v7
// Public security tokens? -> v4
// Distributed tracing/correlation? -> v7
// Audit logs? -> v7
}| Feature | v4 | v7 | |---------|----|----| | Random bits | 122 | ~74 per ms | | Time-sortable | No | Yes | | Timestamp extraction | No | Yes | | DB index performance | Poor | Excellent | | Collision probability | ~2ā»Ā¹Ā²Ā² | ~2ā»ā·ā“ per ms | | Security (unpredictability) | Best | Good (leaks timing) |
When to choose v4:
When to choose v7:
Key trade-offs:
The fundamental insight: UUID v4 optimizes for unpredictability and uniform distribution at the cost of database index efficiency. UUID v7 optimizes for time-ordering and insert performance by prefixing with a timestamp, at the cost of leaking timing information. In most database-backed applications, v7's performance benefits far outweigh the minor security consideration of exposed creation timeāparticularly for entity identifiers that aren't security-sensitive. Reserve v4 for cases where the identifier itself must be unpredictable, like authentication tokens or nonces, where the randomness directly contributes to security.