1use std::hash::{BuildHasher, Hasher};
15use std::sync::atomic::{AtomicU64, Ordering};
16
17pub fn new() -> String {
19 static COUNT: AtomicU64 = AtomicU64::new(0);
20 let count = COUNT.fetch_add(1, Ordering::Relaxed);
21 let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_nanos();
22 let half = |which: u8| {
23 let mut hasher = std::collections::hash_map::RandomState::new().build_hasher();
24 hasher.write_u128(now);
25 hasher.write_u64(count);
26 hasher.write_u32(std::process::id());
27 hasher.write_u8(which);
28 hasher.finish().to_le_bytes()
29 };
30 let mut bytes = [0u8; 16];
31 bytes[..8].copy_from_slice(&half(0));
32 bytes[8..].copy_from_slice(&half(1));
33 bytes[6] = (bytes[6] & 0x0f) | 0x40;
35 bytes[8] = (bytes[8] & 0x3f) | 0x80;
36 let hex: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
37 format!("{}-{}-{}-{}-{}", &hex[..8], &hex[8..12], &hex[12..16], &hex[16..20], &hex[20..])
38}
39
40#[cfg(test)]
41mod tests {
42 #[test]
43 fn ids_are_new_each_time_and_shaped_as_uuids() {
44 let ids: std::collections::BTreeSet<String> = (0..10_000).map(|_| super::new()).collect();
45 assert_eq!(ids.len(), 10_000);
46 for id in ids.iter().take(50) {
47 assert_eq!(id.len(), 36, "{id}");
48 assert_eq!(id.as_bytes()[14], b'4', "{id}");
49 assert!(matches!(id.as_bytes()[19], b'8' | b'9' | b'a' | b'b'), "{id}");
50 assert!(id.chars().all(|c| c == '-' || c.is_ascii_hexdigit()), "{id}");
51 }
52 }
53}