From 0365ab86d1a770e840b39fdebdc0256947c15a91 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Tue, 28 Apr 2026 20:04:57 +0200 Subject: [PATCH] feat: add generic slice choice helper Add OsRandom::choose for callers that want a uniformly selected reference from any non-empty slice. This covers the common generic selection use case without adding collection algorithms such as shuffle. Keep OsRandom::pick as the byte-oriented helper by delegating it through choose. This preserves the existing public API while sharing the usize range sampler and avoiding the old u32 length limit. Document choose, pick, and string_from with working examples, and add a choices example that demonstrates generic slice selection alongside byte alphabets. Test Plan: - cargo test - cargo clippy - cargo clippy --benches - cargo clippy --tests - cargo +nightly fmt Refs: IDEAS.md ergonomics backlog --- examples/choices.rs | 16 ++++++++++++ src/lib.rs | 61 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 examples/choices.rs diff --git a/examples/choices.rs b/examples/choices.rs new file mode 100644 index 0000000..60b4e72 --- /dev/null +++ b/examples/choices.rs @@ -0,0 +1,16 @@ +//! Run with: `cargo run --example choices` + +use ez_urandom::{OsRandom, charset}; + +fn main() -> std::io::Result<()> { + let mut rng = OsRandom::try_new()?; + + let environments = ["dev", "staging", "prod"]; + let environment = rng.choose(&environments)?; + let suffix = rng.pick(charset::HEX_LOWER)?; + + println!("environment: {environment}"); + println!("hex suffix : {}", char::from(suffix)); + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 4e5848f..8bbad4c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,8 @@ //! - Convenience byte readers fill caller-provided buffers. //! - Sampling helpers build unbiased higher-level choices from those primitive //! readers. +//! - Range helpers sample integer spans. +//! - Slice helpers choose caller-owned values by reference. //! - [`charset`] contains reusable ASCII alphabets for string generation. use std::{ @@ -423,16 +425,51 @@ impl OsRandom { /// Returns a uniformly chosen byte from `set`. /// /// # Panics - /// Panics if `set` is empty or longer than `u32::MAX`. + /// Panics if `set` is empty. /// /// # Errors /// Returns any I/O error produced while reading from `/dev/urandom`. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> std::io::Result<()> { + /// let mut rng = ez_urandom::OsRandom::try_new()?; + /// let digit = rng.pick(ez_urandom::charset::DIGITS)?; + /// assert!(digit.is_ascii_digit()); + /// # Ok(()) + /// # } + /// ``` pub fn pick(&mut self, set: &[u8]) -> io::Result { - assert!(!set.is_empty(), "set must not be empty"); - let n = u32::try_from(set.len()).expect("set must fit in u32"); - let i = usize::try_from(self.gen_range_u32(n)?) - .expect("u32 fits in usize on supported platforms"); - Ok(set[i]) + Ok(*self.choose(set)?) + } + + /// Returns a uniformly chosen item from `items`. + /// + /// The returned reference points into the caller-provided slice; no item is + /// cloned or moved. + /// + /// # Panics + /// Panics if `items` is empty. + /// + /// # Errors + /// Returns any I/O error produced while reading from `/dev/urandom`. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> std::io::Result<()> { + /// let mut rng = ez_urandom::OsRandom::try_new()?; + /// let colors = ["red", "green", "blue"]; + /// let color = rng.choose(&colors)?; + /// assert!(colors.contains(color)); + /// # Ok(()) + /// # } + /// ``` + pub fn choose<'a, T>(&mut self, items: &'a [T]) -> io::Result<&'a T> { + assert!(!items.is_empty(), "items must not be empty"); + let i = self.gen_range_usize(items.len())?; + Ok(&items[i]) } /// Returns a `String` of `len` characters drawn uniformly from `set`. @@ -443,6 +480,18 @@ impl OsRandom { /// /// # Errors /// Returns any I/O error produced while reading from `/dev/urandom`. + /// + /// # Examples + /// + /// ``` + /// # fn main() -> std::io::Result<()> { + /// let mut rng = ez_urandom::OsRandom::try_new()?; + /// let token = rng.string_from(ez_urandom::charset::ALPHANUMERIC, 24)?; + /// assert_eq!(token.len(), 24); + /// assert!(token.is_ascii()); + /// # Ok(()) + /// # } + /// ``` pub fn string_from(&mut self, set: &[u8], len: usize) -> io::Result { assert!(set.is_ascii(), "set must contain only ASCII bytes"); let mut buf = vec![0u8; len];