Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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].
Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO we should just bump rust-version and be done with it. Situations like this are what MSRV-aware resolver was introduced for.

Should we do it now or later is a separate question.


# Option: enable SysRng
sys_rng = ["dep:getrandom", "getrandom/sys_rng"]

Expand Down
122 changes: 112 additions & 10 deletions src/distr/uniform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -371,22 +370,40 @@ pub trait UniformSampler: Sized {
}
}

impl<X: SampleUniform> TryFrom<Range<X>> for Uniform<X> {
impl<X: SampleUniform> TryFrom<::core::ops::Range<X>> for Uniform<X> {
type Error = Error;

fn try_from(r: Range<X>) -> Result<Uniform<X>, Error> {
fn try_from(r: ::core::ops::Range<X>) -> Result<Uniform<X>, Error> {
Uniform::new(r.start, r.end)
}
}

impl<X: SampleUniform> TryFrom<RangeInclusive<X>> for Uniform<X> {
#[cfg(feature = "rust_1_96")]
impl<X: SampleUniform> TryFrom<::core::range::Range<X>> for Uniform<X> {
type Error = Error;

fn try_from(r: ::core::range::Range<X>) -> Result<Uniform<X>, Error> {
Uniform::new(r.start, r.end)
}
}

impl<X: SampleUniform> TryFrom<::core::ops::RangeInclusive<X>> for Uniform<X> {
type Error = Error;

fn try_from(r: ::core::ops::RangeInclusive<X>) -> Result<Uniform<X>, Error> {
Uniform::new_inclusive(r.start(), r.end())
}
}

#[cfg(feature = "rust_1_96")]
impl<X: SampleUniform> TryFrom<::core::range::RangeInclusive<X>> for Uniform<X> {
type Error = Error;

fn try_from(r: ::core::range::RangeInclusive<X>) -> Result<Uniform<X>, 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.
Expand Down Expand Up @@ -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<T> {
/// Generate a sample from the given range.
fn sample_single<R: Rng + ?Sized>(self, rng: &mut R) -> Result<T, Error>;
Expand All @@ -429,7 +478,7 @@ pub trait SampleRange<T> {
fn is_empty(&self) -> bool;
}

impl<T: SampleUniform + PartialOrd> SampleRange<T> for Range<T> {
impl<T: SampleUniform + PartialOrd> SampleRange<T> for ::core::ops::Range<T> {
#[inline]
fn sample_single<R: Rng + ?Sized>(self, rng: &mut R) -> Result<T, Error> {
T::Sampler::sample_single(self.start, self.end, rng)
Expand All @@ -441,7 +490,20 @@ impl<T: SampleUniform + PartialOrd> SampleRange<T> for Range<T> {
}
}

impl<T: SampleUniform + PartialOrd> SampleRange<T> for RangeInclusive<T> {
#[cfg(feature = "rust_1_96")]
impl<T: SampleUniform + PartialOrd> SampleRange<T> for ::core::range::Range<T> {
#[inline]
fn sample_single<R: Rng + ?Sized>(self, rng: &mut R) -> Result<T, Error> {
T::Sampler::sample_single(self.start, self.end, rng)
}

#[inline]
fn is_empty(&self) -> bool {
!(self.start < self.end)
}
}

impl<T: SampleUniform + PartialOrd> SampleRange<T> for ::core::ops::RangeInclusive<T> {
#[inline]
fn sample_single<R: Rng + ?Sized>(self, rng: &mut R) -> Result<T, Error> {
T::Sampler::sample_single_inclusive(self.start(), self.end(), rng)
Expand All @@ -453,9 +515,22 @@ impl<T: SampleUniform + PartialOrd> SampleRange<T> for RangeInclusive<T> {
}
}

#[cfg(feature = "rust_1_96")]
impl<T: SampleUniform + PartialOrd> SampleRange<T> for ::core::range::RangeInclusive<T> {
#[inline]
fn sample_single<R: Rng + ?Sized>(self, rng: &mut R) -> Result<T, Error> {
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<R: Rng + ?Sized>(self, rng: &mut R) -> Result<$t, Error> {
<$t as SampleUniform>::Sampler::sample_single(0, self.end, rng)
Expand All @@ -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<R: Rng + ?Sized>(self, rng: &mut R) -> Result<$t, Error> {
<$t as SampleUniform>::Sampler::sample_single_inclusive(0, self.end, rng)
Expand All @@ -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<R: Rng + ?Sized>(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
}
}
};
}

Expand Down Expand Up @@ -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<T: SampleUniform + Copy + fmt::Debug + PartialEq>(
Expand Down
17 changes: 9 additions & 8 deletions src/rng.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand All @@ -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::<u32, _>(..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
Expand Down
Loading