From 576efe25dce4920f8374832d74057bf9a6f9eb6b Mon Sep 17 00:00:00 2001 From: Kristof Mattei <864376+kristof-mattei@users.noreply.github.com> Date: Fri, 29 May 2026 10:46:47 -0700 Subject: [PATCH 1/5] feat: add support for the Rust range types, guarded by a `new_range_api` flag --- Cargo.toml | 2 ++ src/distr/uniform.rs | 77 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9bbef85456..19edbf0d6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,8 @@ unbiased = [] # Deprecated option: enable logging log = [] +new_range_api = [] + [dependencies] rand_core = { version = "0.10.0", default-features = false } serde = { version = "1.0.103", features = ["derive"], optional = true } diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index c61a518e9f..6b4137aded 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 = "new_range_api")] +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 = "new_range_api")] +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. @@ -429,7 +446,20 @@ 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) + } + + #[inline] + fn is_empty(&self) -> bool { + !(self.start < self.end) + } +} + +#[cfg(feature = "new_range_api")] +impl SampleRange for ::core::range::Range { #[inline] fn sample_single(self, rng: &mut R) -> Result { T::Sampler::sample_single(self.start, self.end, rng) @@ -441,7 +471,7 @@ impl SampleRange for Range { } } -impl SampleRange for RangeInclusive { +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 +483,22 @@ impl SampleRange for RangeInclusive { } } +#[cfg(feature = "new_range_api")] +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 +510,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 +521,24 @@ macro_rules! impl_sample_range_u { false } } + + // `core::range::RangeTo` is set to be a re-export of `core::ops::RangeTo`: + // > A Rust version in the near future will also add `core::range::RangeFull` and `core::range::RangeTo` + // as re-exports from `core::ops` (these do not implement `Iterator` and already implement `Copy`) + // Source: https://blog.rust-lang.org/2026/05/28/Rust-1.96.0/#new-range-types + + #[cfg(feature = "new_range_api")] + 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 + } + } }; } From e13bba79f853b980877245f015669ef415376502 Mon Sep 17 00:00:00 2001 From: Kristof Mattei <864376+kristof-mattei@users.noreply.github.com> Date: Sat, 30 May 2026 09:58:36 -0700 Subject: [PATCH 2/5] fix: rename feature to indicate rustc version requirements --- Cargo.toml | 3 ++- src/distr/uniform.rs | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 19edbf0d6c..80951a3f3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,8 @@ unbiased = [] # Deprecated option: enable logging log = [] -new_range_api = [] +# Support the std::range types stabilised in Rust 1.96.0. +rust_1_96 = [] [dependencies] rand_core = { version = "0.10.0", default-features = false } diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index 6b4137aded..99dd33b33d 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -378,7 +378,7 @@ impl TryFrom<::core::ops::Range> for Uniform { } } -#[cfg(feature = "new_range_api")] +#[cfg(feature = "rust_1_96")] impl TryFrom<::core::range::Range> for Uniform { type Error = Error; @@ -395,7 +395,7 @@ impl TryFrom<::core::ops::RangeInclusive> for Uniform { } } -#[cfg(feature = "new_range_api")] +#[cfg(feature = "rust_1_96")] impl TryFrom<::core::range::RangeInclusive> for Uniform { type Error = Error; @@ -458,7 +458,7 @@ impl SampleRange for ::core::ops::Range { } } -#[cfg(feature = "new_range_api")] +#[cfg(feature = "rust_1_96")] impl SampleRange for ::core::range::Range { #[inline] fn sample_single(self, rng: &mut R) -> Result { @@ -483,7 +483,7 @@ impl SampleRange for ::core::ops::RangeInclusi } } -#[cfg(feature = "new_range_api")] +#[cfg(feature = "rust_1_96")] impl SampleRange for ::core::range::RangeInclusive { #[inline] fn sample_single(self, rng: &mut R) -> Result { @@ -527,7 +527,7 @@ macro_rules! impl_sample_range_u { // as re-exports from `core::ops` (these do not implement `Iterator` and already implement `Copy`) // Source: https://blog.rust-lang.org/2026/05/28/Rust-1.96.0/#new-range-types - #[cfg(feature = "new_range_api")] + #[cfg(feature = "rust_1_96")] impl SampleRange<$t> for ::core::range::RangeToInclusive<$t> { #[inline] fn sample_single(self, rng: &mut R) -> Result<$t, Error> { From 31982e5394e891d023c06c2079de73c927e8c6d5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 23 Jun 2026 08:48:14 +0000 Subject: [PATCH 3/5] Tweak docs for RngExt::random_range and SampleRange --- src/distr/uniform.rs | 36 ++++++++++++++++++++++++++++++++++-- src/rng.rs | 17 +++++++++-------- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index 9561842d5a..8702687f5a 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -436,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; 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 From 620cea72f32b69a763621a7d461a89af3cbe079e Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 23 Jun 2026 09:01:42 +0000 Subject: [PATCH 4/5] Add construction test for TryFrom for Uniform --- src/distr/uniform.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/distr/uniform.rs b/src/distr/uniform.rs index 8702687f5a..643deb84b2 100644 --- a/src/distr/uniform.rs +++ b/src/distr/uniform.rs @@ -643,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( From 67c419ae5f75487072b416f10536a18f6599de00 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Tue, 23 Jun 2026 09:14:26 +0000 Subject: [PATCH 5/5] CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) 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].