Skip to content

Let Fill target element types; move to rand_core; support min_specialization #1651

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
- name: rand
run: cargo doc --all-features --no-deps
- name: rand_core
run: cargo doc --all-features --package rand_core --no-deps
run: cargo doc --features std,os_rng,serde --package rand_core --no-deps
- name: rand_chacha
run: cargo doc --all-features --package rand_chacha --no-deps
- name: rand_pcg
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.
## Additions
- Pub export `Xoshiro128PlusPlus`, `Xoshiro256PlusPlus` prngs (#1649)

### Changes
- Move `Fill` trait to `rand_core` (#1651)

## [0.9.2 — 2025-07-20]
### Deprecated
- Deprecate `rand::rngs::mock` module and `StepRng` generator (#1634)
Expand Down
4 changes: 4 additions & 0 deletions rand_core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Additions
- Add `Fill` trait (#1651)

## [0.9.3] — 2025-02-29
### Other
- Remove `zerocopy` dependency (#1607)
Expand Down
15 changes: 12 additions & 3 deletions rand_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ rust-version = "1.63"

[package.metadata.docs.rs]
# To build locally:
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --no-deps --open
all-features = true
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --features std,os_rng,serde --no-deps --open
features = ["std", "os_rng", "serde"]
rustdoc-args = ["--generate-link-to-definition"]

[package.metadata.playground]
Expand All @@ -27,7 +27,16 @@ all-features = true
[features]
std = ["getrandom?/std"]
os_rng = ["dep:getrandom"]
serde = ["dep:serde"] # enables serde for BlockRng wrapper

# Enable serde for BlockRng wrapper
serde = ["dep:serde"]

# Enable specialization of Fill for RNGs
# This is an unstable feature!
min_specialization = []
# Enable specialized Fill impls for BlockRng
# This requires an even less stable feature!
specialization = ["min_specialization"]

[dependencies]
serde = { version = "1", features = ["derive"], optional = true }
Expand Down
36 changes: 36 additions & 0 deletions rand_core/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,24 @@ impl<R: BlockRngCore<Item = u32>> RngCore for BlockRng<R> {
}
}

#[cfg(feature = "specialization")]
impl<R: BlockRngCore<Item = u32>> crate::Fill<BlockRng<R>> for u32 {
fn fill_slice(mut this: &mut [Self], rng: &mut BlockRng<R>) {
while !this.is_empty() {
if rng.index >= rng.results.as_ref().len() {
rng.generate_and_set(0);
}

let len = core::cmp::min(this.len(), rng.results.as_ref().len() - rng.index);
let next_index = rng.index + len;
this[..len].copy_from_slice(&rng.results.as_ref()[rng.index..next_index]);

rng.index = next_index;
this = &mut this[len..];
}
}
}

impl<R: BlockRngCore + SeedableRng> SeedableRng for BlockRng<R> {
type Seed = R::Seed;

Expand Down Expand Up @@ -397,6 +415,24 @@ impl<R: BlockRngCore<Item = u64>> RngCore for BlockRng64<R> {
}
}

#[cfg(feature = "specialization")]
impl<R: BlockRngCore<Item = u64>> crate::Fill<BlockRng64<R>> for u64 {
fn fill_slice(mut this: &mut [Self], rng: &mut BlockRng64<R>) {
while !this.is_empty() {
if rng.index >= rng.results.as_ref().len() {
rng.generate_and_set(0);
}

let len = core::cmp::min(this.len(), rng.results.as_ref().len() - rng.index);
let next_index = rng.index + len;
this[..len].copy_from_slice(&rng.results.as_ref()[rng.index..next_index]);

rng.index = next_index;
this = &mut this[len..];
}
}
}

impl<R: BlockRngCore + SeedableRng> SeedableRng for BlockRng64<R> {
type Seed = R::Seed;

Expand Down
109 changes: 109 additions & 0 deletions rand_core/src/fill.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright 2025 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! `Fill` trait

use super::RngCore;
use core::num::Wrapping;
use core::{mem, slice};

/// Support filling a slice with random data
///
/// This trait allows slices of "plain data" types to be efficiently filled
/// with random data.
///
/// Implementations are expected to be portable across machines unless
/// clearly documented otherwise (see the
/// [Chapter on Portability](https://rust-random.github.io/book/portability.html)).
/// The implementations provided achieve this by byte-swapping on big-endian
/// machines.
pub trait Fill<R: RngCore + ?Sized>: Sized {
/// Fill this with random data
fn fill_slice(this: &mut [Self], rng: &mut R);
}

impl<R: RngCore + ?Sized> Fill<R> for u8 {
#[cfg(feature = "min_specialization")]
default fn fill_slice(this: &mut [Self], rng: &mut R) {
rng.fill_bytes(this)
}

#[cfg(not(feature = "min_specialization"))]
fn fill_slice(this: &mut [Self], rng: &mut R) {
rng.fill_bytes(this)
}
}

/// Call target for unsafe macros
const unsafe fn __unsafe() {}

/// Implement `Fill` for given type `$t`.
///
/// # Safety
/// All bit patterns of `[u8; size_of::<$t>()]` must represent values of `$t`.
macro_rules! impl_fill {
() => {};
(to_le! plain $x:ident) => {
$x.to_le()
};
(to_le! wrapping $x:ident) => {
Wrapping($x.0.to_le())
};
(fill_slice! $t:ty, $to_le:tt $($maybe_default:tt)?) => {
$($maybe_default)? fn fill_slice(this: &mut [Self], rng: &mut R) {
if this.len() > 0 {
let size = mem::size_of_val(this);
rng.fill_bytes(
// SAFETY: `this` non-null and valid for reads and writes within its `size`
// bytes. `this` meets the alignment requirements of `&mut [u8]`.
// The contents of `this` are initialized. Both `[u8]` and `[$t]` are valid
// for all bit-patterns of their contents (note that the SAFETY requirement
// on callers of this macro). `this` is not borrowed.
unsafe {
slice::from_raw_parts_mut(this.as_mut_ptr()
as *mut u8,
size
)
}
);
for x in this {
*x = impl_fill!(to_le! $to_le x);
}
}
}
};
($t:ty) => {{
// Force caller to wrap with an `unsafe` block
__unsafe();

impl<R: RngCore + ?Sized> Fill<R> for $t {
#[cfg(any(feature = "min_specialization", feature = "specialization"))]
impl_fill!(fill_slice! $t, plain default);
#[cfg(not(any(feature = "min_specialization", feature = "specialization")))]
impl_fill!(fill_slice! $t, plain);
}

impl<R: RngCore + ?Sized> Fill<R> for Wrapping<$t> {
#[cfg(any(feature = "min_specialization", feature = "specialization"))]
impl_fill!(fill_slice! $t, wrapping default);
#[cfg(not(any(feature = "min_specialization", feature = "specialization")))]
impl_fill!(fill_slice! $t, wrapping);
}}
};
($t:ty, $($tt:ty,)*) => {{
impl_fill!($t);
// TODO: this could replace above impl once Rust #32463 is fixed
// impl_fill!(Wrapping<$t>);
impl_fill!($($tt,)*);
}}
}

// SAFETY: All bit patterns of `[u8; size_of::<$t>()]` represent values of `u*`.
const _: () = unsafe { impl_fill!(u16, u32, u64, u128,) };
// SAFETY: All bit patterns of `[u8; size_of::<$t>()]` represent values of `i*`.
const _: () = unsafe { impl_fill!(i8, i16, i32, i64, i128,) };
7 changes: 7 additions & 0 deletions rand_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,25 @@
#![doc(test(attr(allow(unused_variables), deny(warnings))))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![no_std]
#![cfg_attr(feature = "specialization", feature(specialization))]
#![cfg_attr(
all(feature = "min_specialization", not(feature = "specialization")),
feature(min_specialization)
)]

#[cfg(feature = "std")]
extern crate std;

use core::{fmt, ops::DerefMut};

pub mod block;
mod fill;
pub mod impls;
pub mod le;
#[cfg(feature = "os_rng")]
mod os;

pub use fill::Fill;
#[cfg(feature = "os_rng")]
pub use os::{OsError, OsRng};

Expand Down
40 changes: 40 additions & 0 deletions rand_core/tests/fill.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2025 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![allow(unused)]
#![cfg_attr(feature = "min_specialization", feature(min_specialization))]

use rand_core::{Fill, RngCore};

// Test that Fill may be implemented for externally-defined types
struct MyInt(i32);
impl<R: RngCore + ?Sized> Fill<R> for MyInt {
fn fill_slice(this: &mut [Self], rng: &mut R) {
todo!()
}
}

// Test specialization on a local RNG
struct MyRng;
impl RngCore for MyRng {
fn next_u32(&mut self) -> u32 {
todo!()
}
fn next_u64(&mut self) -> u64 {
todo!()
}
fn fill_bytes(&mut self, _: &mut [u8]) {
todo!()
}
}
#[cfg(feature = "min_specialization")]
impl Fill<MyRng> for u64 {
fn fill_slice(this: &mut [Self], rng: &mut MyRng) {
todo!()
}
}
8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ macro_rules! error { ($($x:tt)*) => (
pub use rand_core;

// Re-exports from rand_core
pub use rand_core::{CryptoRng, RngCore, SeedableRng, TryCryptoRng, TryRngCore};
pub use rand_core::{CryptoRng, Fill, RngCore, SeedableRng, TryCryptoRng, TryRngCore};

// Public modules
pub mod distr;
Expand All @@ -124,7 +124,7 @@ pub fn thread_rng() -> crate::rngs::ThreadRng {
rng()
}

pub use rng::{Fill, Rng};
pub use rng::Rng;

#[cfg(feature = "thread_rng")]
use crate::distr::{Distribution, StandardUniform};
Expand Down Expand Up @@ -293,8 +293,8 @@ pub fn random_ratio(numerator: u32, denominator: u32) -> bool {
#[cfg(feature = "thread_rng")]
#[inline]
#[track_caller]
pub fn fill<T: Fill + ?Sized>(dest: &mut T) {
dest.fill(&mut rng())
pub fn fill<T: Fill<rngs::ThreadRng>>(dest: &mut [T]) {
Fill::fill_slice(dest, &mut rng())
}

#[cfg(test)]
Expand Down
Loading
Loading