diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d9f126945..4c3a8c2072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,12 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update. - Document required output order of fn `partial_shuffle` and apply `#[must_use]` ([#1769]) - Avoid usage of `unsafe` in contexts where non-local memory corruption could invalidate contract ([#1791]) +### Additions +- Add `rust_1_96` feature gate, supporting sampling over `std::range` types ([#1792]) + [#1769]: https://github.com/rust-random/rand/pull/1769 [#1791]: https://github.com/rust-random/rand/pull/1791 +[#1792]: https://github.com/rust-random/rand/pull/1792 ## [0.10.1] — 2026-02-11 This release includes a fix for a soundness bug; see [#1763]. diff --git a/Cargo.toml b/Cargo.toml index 9bbef85456..45e3afcb09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,9 @@ std = ["alloc", "getrandom?/std"] # Option: "alloc" enables support for Vec and Box when not using "std" alloc = [] +# Option: support the std::range types (requires Rust >= 1.96.0). +rust_1_96 = [] + # Option: enable SysRng sys_rng = ["dep:getrandom", "getrandom/sys_rng"] diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index c61a518e9f..643deb84b2 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -110,7 +110,6 @@ mod other; pub use other::{UniformChar, UniformDuration}; use core::fmt; -use core::ops::{Range, RangeInclusive, RangeTo, RangeToInclusive}; use crate::Rng; use crate::distr::Distribution; @@ -371,15 +370,24 @@ pub trait UniformSampler: Sized { } } -impl TryFrom> for Uniform { +impl TryFrom<::core::ops::Range> for Uniform { type Error = Error; - fn try_from(r: Range) -> Result, Error> { + fn try_from(r: ::core::ops::Range) -> Result, Error> { Uniform::new(r.start, r.end) } } -impl TryFrom> for Uniform { +#[cfg(feature = "rust_1_96")] +impl TryFrom<::core::range::Range> for Uniform { + type Error = Error; + + fn try_from(r: ::core::range::Range) -> Result, Error> { + Uniform::new(r.start, r.end) + } +} + +impl TryFrom<::core::ops::RangeInclusive> for Uniform { type Error = Error; fn try_from(r: ::core::ops::RangeInclusive) -> Result, Error> { @@ -387,6 +395,15 @@ impl TryFrom> for Uniform { } } +#[cfg(feature = "rust_1_96")] +impl TryFrom<::core::range::RangeInclusive> for Uniform { + type Error = Error; + + fn try_from(r: ::core::range::RangeInclusive) -> Result, Error> { + Uniform::new_inclusive(r.start, r.last) + } +} + /// Helper trait similar to [`Borrow`] but implemented /// only for [`SampleUniform`] and references to [`SampleUniform`] /// in order to resolve ambiguity issues. @@ -419,8 +436,40 @@ where /// Range that supports generating a single sample efficiently. /// -/// Any type implementing this trait can be used to specify the sampled range -/// for `Rng::random_range`. +/// See [`RngExt::random_range`] for examples; the method provides a convenient +/// interface over this type. +/// +/// # Examples +/// +/// ``` +/// use rand::distr::uniform::SampleRange; +/// let mut rng = rand::rng(); +/// +/// // Range and InclusiveRange are supported for many types: +/// assert_eq!((9..10).sample_single(&mut rng), Ok(9)); +/// assert_eq!(('a'..='a').sample_single(&mut rng), Ok('a')); +/// +/// // RangeTo and RangeToInclusive are supported for unsigned integers: +/// assert_eq!((..1u8).sample_single(&mut rng), Ok(0)); +/// assert!((..=10u32).sample_single(&mut rng).unwrap() <= 10); +#[cfg_attr(feature = "rust_1_96", doc = "")] +#[cfg_attr( + feature = "rust_1_96", + doc = "// std::range types are supported using feature rust_1_96:" +)] +#[cfg_attr( + feature = "rust_1_96", + doc = "assert_eq!((std::range::Range::from(9..10)).sample_single(&mut rng), Ok(9));" +)] +#[cfg_attr( + feature = "rust_1_96", + doc = "assert_eq!((std::range::RangeInclusive::from(9..=9)).sample_single(&mut rng), Ok(9));" +)] +#[cfg_attr( + feature = "rust_1_96", + doc = "assert_eq!((std::range::RangeToInclusive::from(..=0usize)).sample_single(&mut rng), Ok(0));" +)] +/// ``` pub trait SampleRange { /// Generate a sample from the given range. fn sample_single(self, rng: &mut R) -> Result; @@ -429,7 +478,7 @@ pub trait SampleRange { fn is_empty(&self) -> bool; } -impl SampleRange for Range { +impl SampleRange for ::core::ops::Range { #[inline] fn sample_single(self, rng: &mut R) -> Result { T::Sampler::sample_single(self.start, self.end, rng) @@ -441,7 +490,20 @@ impl SampleRange for Range { } } -impl SampleRange for RangeInclusive { +#[cfg(feature = "rust_1_96")] +impl SampleRange for ::core::range::Range { + #[inline] + fn sample_single(self, rng: &mut R) -> Result { + T::Sampler::sample_single(self.start, self.end, rng) + } + + #[inline] + fn is_empty(&self) -> bool { + !(self.start < self.end) + } +} + +impl SampleRange for ::core::ops::RangeInclusive { #[inline] fn sample_single(self, rng: &mut R) -> Result { T::Sampler::sample_single_inclusive(self.start(), self.end(), rng) @@ -453,9 +515,22 @@ impl SampleRange for RangeInclusive { } } +#[cfg(feature = "rust_1_96")] +impl SampleRange for ::core::range::RangeInclusive { + #[inline] + fn sample_single(self, rng: &mut R) -> Result { + T::Sampler::sample_single_inclusive(self.start, self.last, rng) + } + + #[inline] + fn is_empty(&self) -> bool { + !(self.start <= self.last) + } +} + macro_rules! impl_sample_range_u { ($t:ty) => { - impl SampleRange<$t> for RangeTo<$t> { + impl SampleRange<$t> for ::core::ops::RangeTo<$t> { #[inline] fn sample_single(self, rng: &mut R) -> Result<$t, Error> { <$t as SampleUniform>::Sampler::sample_single(0, self.end, rng) @@ -467,7 +542,7 @@ macro_rules! impl_sample_range_u { } } - impl SampleRange<$t> for RangeToInclusive<$t> { + impl SampleRange<$t> for ::core::ops::RangeToInclusive<$t> { #[inline] fn sample_single(self, rng: &mut R) -> Result<$t, Error> { <$t as SampleUniform>::Sampler::sample_single_inclusive(0, self.end, rng) @@ -478,6 +553,19 @@ macro_rules! impl_sample_range_u { false } } + + #[cfg(feature = "rust_1_96")] + impl SampleRange<$t> for ::core::range::RangeToInclusive<$t> { + #[inline] + fn sample_single(self, rng: &mut R) -> Result<$t, Error> { + <$t as SampleUniform>::Sampler::sample_single_inclusive(0, self.last, rng) + } + + #[inline] + fn is_empty(&self) -> bool { + false + } + } }; } @@ -555,6 +643,20 @@ mod tests { } } + #[test] + #[cfg(feature = "rust_1_96")] + fn test_std_range_construction() { + let mut rng = crate::test::rng(0); + + assert_eq!(Uniform::try_from(7..8).unwrap().sample(&mut rng), 7); + let r = core::range::Range::from(7..8); + assert_eq!(Uniform::try_from(r).unwrap().sample(&mut rng), 7); + + assert_eq!(Uniform::try_from(7..=7).unwrap().sample(&mut rng), 7); + let r = core::range::RangeInclusive::from(7..=7); + assert_eq!(Uniform::try_from(r).unwrap().sample(&mut rng), 7); + } + #[test] fn value_stability() { fn test_samples( diff --git a/src/rng.rs b/src/rng.rs index 564dc9f1ef..e5cbf6d7b1 100644 --- a/src/rng.rs +++ b/src/rng.rs @@ -129,8 +129,11 @@ pub trait RngExt: Rng { /// made from the given range. See also the [`Uniform`] distribution /// type which may be faster if sampling from the same range repeatedly. /// - /// All types support `low..high_exclusive` and `low..=high` range syntax. - /// Unsigned integer types also support `..high_exclusive` and `..=high` syntax. + /// All supported types may be sampled with `low..high_exclusive` (`Range`) + /// and `low..=high` (`RangeInclusive`) syntax. Unsigned integer types also + /// support `..high_exclusive` (`RangeTo`) and `..=high` + /// (`RangeToInclusive`) syntax. Support for the new [`std::range`] types is + /// currently gated behind the `rust_1_96` feature. /// /// # Panics /// @@ -144,14 +147,12 @@ pub trait RngExt: Rng { /// let mut rng = rand::rng(); /// /// // Exclusive range - /// let n: u32 = rng.random_range(..10); - /// println!("{}", n); - /// let m: f64 = rng.random_range(-40.0..1.3e5); - /// println!("{}", m); + /// println!("{}", rng.random_range::(..10)); + /// println!("{}", rng.random_range(-40.0..1.3e5)); /// /// // Inclusive range - /// let n: u32 = rng.random_range(..=10); - /// println!("{}", n); + /// println!("{}", rng.random_range(-10..=10)); + /// println!("{}", rng.random_range('a'..='z')); /// ``` /// /// [`Uniform`]: distr::uniform::Uniform