Skip to content

Commit

Permalink
Implement fill_via_chunks without using zerocopy or unsafe.
Browse files Browse the repository at this point in the history
Reduce the use of `zerocopy` as a step towards eliminating the
dependency.
  • Loading branch information
briansmith committed Feb 4, 2025
1 parent bfd1826 commit 7a14dfe
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 27 deletions.
1 change: 0 additions & 1 deletion rand_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,3 @@ serde = ["dep:serde"] # enables serde for BlockRng wrapper
[dependencies]
serde = { version = "1", features = ["derive"], optional = true }
getrandom = { version = "0.3.0", optional = true }
zerocopy = { version = "0.8.0", default-features = false }
60 changes: 34 additions & 26 deletions rand_core/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
//! non-reproducible sources (e.g. `OsRng`) need not bother with it.
use crate::RngCore;
use core::cmp::min;
use zerocopy::{Immutable, IntoBytes};

/// Implement `next_u64` via `next_u32`, little-endian order.
pub fn next_u64_via_u32<R: RngCore + ?Sized>(rng: &mut R) -> u64 {
Expand Down Expand Up @@ -53,17 +51,22 @@ pub fn fill_bytes_via_next<R: RngCore + ?Sized>(rng: &mut R, dest: &mut [u8]) {
}
}

trait Observable: IntoBytes + Immutable + Copy {
fn to_le(self) -> Self;
trait Observable: Copy {
type Bytes: Sized + AsRef<[u8]>;
fn to_le_bytes(self) -> Self::Bytes;
}
impl Observable for u32 {
fn to_le(self) -> Self {
self.to_le()
type Bytes = [u8; 4];

fn to_le_bytes(self) -> Self::Bytes {
Self::to_le_bytes(self)
}
}
impl Observable for u64 {
fn to_le(self) -> Self {
self.to_le()
type Bytes = [u8; 8];

fn to_le_bytes(self) -> Self::Bytes {
Self::to_le_bytes(self)
}
}

Expand All @@ -72,32 +75,35 @@ impl Observable for u64 {
/// Returns `(n, byte_len)`. `src[..n]` is consumed (and possibly mutated),
/// `dest[..byte_len]` is filled. `src[n..]` and `dest[byte_len..]` are left
/// unaltered.
fn fill_via_chunks<T: Observable>(src: &mut [T], dest: &mut [u8]) -> (usize, usize) {
fn fill_via_chunks<T: Observable>(src: &[T], dest: &mut [u8]) -> (usize, usize) {
let size = core::mem::size_of::<T>();
let byte_len = min(core::mem::size_of_val(src), dest.len());
let num_chunks = (byte_len + size - 1) / size;

// Byte-swap for portability of results. This must happen before copying
// since the size of dest is not guaranteed to be a multiple of T or to be
// sufficiently aligned.
if cfg!(target_endian = "big") {
for x in &mut src[..num_chunks] {
*x = x.to_le();
}
}

dest[..byte_len].copy_from_slice(&<[T]>::as_bytes(&src[..num_chunks])[..byte_len]);
// Always use little endian for portability of results.

(num_chunks, byte_len)
let mut dest = dest.chunks_exact_mut(size);
let mut src = src.iter();

let zipped = dest.by_ref().zip(src.by_ref());
let num_chunks = zipped.len();
zipped.for_each(|(dest, src)| dest.copy_from_slice(src.to_le_bytes().as_ref()));

let byte_len = num_chunks * size;
if let (dest_tail @ [_, ..], Some(src)) = (dest.into_remainder(), src.next()) {
let n = dest_tail.len();
dest_tail.copy_from_slice(&src.to_le_bytes().as_ref()[..n]);
(num_chunks + 1, byte_len + n)
} else {
(num_chunks, byte_len)
}
}

/// Implement `fill_bytes` by reading chunks from the output buffer of a block
/// based RNG.
///
/// The return values are `(consumed_u32, filled_u8)`.
///
/// On big-endian systems, endianness of `src[..consumed_u32]` values is
/// swapped. No other adjustments to `src` are made.
/// `src` is not modified; it is taken as a `&mut` reference for backward
/// compatibility with previous versions that did change it.
///
/// `filled_u8` is the number of filled bytes in `dest`, which may be less than
/// the length of `dest`.
Expand Down Expand Up @@ -125,6 +131,7 @@ fn fill_via_chunks<T: Observable>(src: &mut [T], dest: &mut [u8]) -> (usize, usi
/// }
/// ```
pub fn fill_via_u32_chunks(src: &mut [u32], dest: &mut [u8]) -> (usize, usize) {
// TODO(SemVer): src: `&[u32]` as we don't mutate it.
fill_via_chunks(src, dest)
}

Expand All @@ -133,8 +140,8 @@ pub fn fill_via_u32_chunks(src: &mut [u32], dest: &mut [u8]) -> (usize, usize) {
///
/// The return values are `(consumed_u64, filled_u8)`.
///
/// On big-endian systems, endianness of `src[..consumed_u64]` values is
/// swapped. No other adjustments to `src` are made.
/// `src` is not modified; it is taken as a `&mut` reference for backward
/// compatibility with previous versions that did change it.
///
/// `filled_u8` is the number of filled bytes in `dest`, which may be less than
/// the length of `dest`.
Expand All @@ -143,6 +150,7 @@ pub fn fill_via_u32_chunks(src: &mut [u32], dest: &mut [u8]) -> (usize, usize) {
///
/// See `fill_via_u32_chunks` for an example.
pub fn fill_via_u64_chunks(src: &mut [u64], dest: &mut [u8]) -> (usize, usize) {
// TODO(SemVer): src: `&[u64]` as we don't mutate it.
fill_via_chunks(src, dest)
}

Expand Down

0 comments on commit 7a14dfe

Please sign in to comment.