From 808e0482c38b43f4bfd11a00d124d66583bd215a Mon Sep 17 00:00:00 2001 From: ddidderr Date: Tue, 28 Apr 2026 20:07:47 +0200 Subject: [PATCH] feat: support cloning random handles Add OsRandom::try_clone for fallible duplication of the underlying /dev/urandom file handle, and implement Clone as the infallible convenience form. This gives independent call sites their own OsRandom values without adding interior mutability or shared buffering to the core type. The fallible method is documented as the preferred API when callers need to handle a failed file-handle duplication. Clone mirrors Default by panicking only for the convenience path. Document both cloning forms with doctested examples and add a runnable clone example that uses independent handles for IDs and string helpers. Test Plan: - cargo test - cargo clippy - cargo clippy --benches - cargo clippy --tests - cargo +nightly fmt Refs: IDEAS.md ergonomics backlog --- examples/clone.rs | 15 +++++++++++++++ src/lib.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 examples/clone.rs diff --git a/examples/clone.rs b/examples/clone.rs new file mode 100644 index 0000000..3df12ed --- /dev/null +++ b/examples/clone.rs @@ -0,0 +1,15 @@ +//! Run with: `cargo run --example clone` + +use ez_urandom::OsRandom; + +fn main() -> std::io::Result<()> { + let mut ids = OsRandom::try_new()?; + let mut tokens = ids.try_clone()?; + let mut fallback = tokens.clone(); + + println!("id : {}", ids.get_u64()?); + println!("token : {}", tokens.token(16)?); + println!("fallback: {}", fallback.hex(16)?); + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 6c716a2..59b9cc9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -329,6 +329,35 @@ impl OsRandom { self.devurandom.read_exact(buf) } + /// Duplicates this handle to `/dev/urandom`. + /// + /// The cloned value has its own [`File`] handle, so separate call sites can + /// own independent `OsRandom` values while reading from the same + /// operating-system randomness source. + /// + /// # Errors + /// Returns any I/O error produced while duplicating the underlying file + /// handle. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> std::io::Result<()> { + /// let mut first = ez_urandom::OsRandom::try_new()?; + /// let mut second = first.try_clone()?; + /// + /// let a = first.get_u64()?; + /// let b = second.get_u64()?; + /// # let _ = (a, b); + /// # Ok(()) + /// # } + /// ``` + pub fn try_clone(&self) -> io::Result { + Ok(Self { + devurandom: self.devurandom.try_clone()?, + }) + } + os_random_get_integer_impls!( u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize ); @@ -353,6 +382,26 @@ impl Default for OsRandom { } } +/// Duplicates the underlying `/dev/urandom` handle. +/// +/// Prefer [`OsRandom::try_clone`] when the caller should decide how to handle an +/// I/O failure while duplicating the file handle. +/// +/// # Examples +/// +/// ``` +/// let rng = ez_urandom::OsRandom::default(); +/// let mut cloned = rng.clone(); +/// let value = cloned.get_u32().expect("/dev/urandom read failed"); +/// # let _ = value; +/// ``` +impl Clone for OsRandom { + fn clone(&self) -> Self { + self.try_clone() + .expect("/dev/urandom handle should be cloneable") + } +} + /// Uniform sampling helpers. /// /// Range methods use rejection sampling on top of the matching primitive