From bff0b5bfcfe471d48306ad7afd26561cd41b1f87 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Mon, 13 Feb 2023 13:49:27 -0800 Subject: [PATCH 1/2] Add no_std mode --- .github/workflows/ci.yml | 2 ++ Cargo.toml | 4 +++ src/lib.rs | 54 ++++++++++++++++++++++++++++++---------- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72fd7c8..62b3ec4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,8 @@ jobs: if: startsWith(matrix.rust, 'nightly') run: cargo check -Z features=dev_dep - run: cargo test + - name: Build with no default features + run: cargo build --no-default-features - name: Test wasm run: wasm-pack test --headless --chrome if: startsWith(matrix.os, 'ubuntu') diff --git a/Cargo.toml b/Cargo.toml index 8c80502..8024161 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,10 @@ keywords = ["simple", "fast", "rand", "random", "wyrand"] categories = ["algorithms"] exclude = ["/.*"] +[features] +default = ["std"] +std = [] + [target.'cfg(all(target_arch = "wasm32", not(target_os = "wasi")))'.dependencies] instant = "0.1" diff --git a/src/lib.rs b/src/lib.rs index dc3fd31..fe1d279 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,26 +64,39 @@ //! let mut rng = fastrand::Rng::new(); //! let mut bytes: Vec = repeat_with(|| rng.u8(..)).take(10_000).collect(); //! ``` - +//! +//! # Features +//! +//! - `std` (enabled by default): Enables the `std` library. This is required for the global +//! generator and global entropy. Without this feature, [`Rng`] can only be instantiated using +//! the [`with_seed`](Rng::with_seed) method. + +#![cfg_attr(not(feature = "std"), no_std)] #![forbid(unsafe_code)] #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] +use core::convert::{TryFrom, TryInto}; +use core::ops::{Bound, RangeBounds}; + +#[cfg(feature = "std")] use std::cell::Cell; +#[cfg(feature = "std")] use std::collections::hash_map::DefaultHasher; -use std::convert::TryInto; +#[cfg(feature = "std")] use std::hash::{Hash, Hasher}; -use std::ops::{Bound, RangeBounds}; +#[cfg(feature = "std")] use std::thread; -#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] +#[cfg(all(feature = "std", target_arch = "wasm32", not(target_os = "wasi")))] use instant::Instant; -#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] +#[cfg(all(feature = "std", not(all(target_arch = "wasm32", not(target_os = "wasi")))))] use std::time::Instant; /// A random number generator. #[derive(Debug, PartialEq, Eq)] pub struct Rng(u64); +#[cfg(feature = "std")] impl Default for Rng { #[inline] fn default() -> Rng { @@ -175,6 +188,7 @@ impl Rng { } } +#[cfg(feature = "std")] thread_local! { static RNG: Cell = Cell::new(Rng({ let mut hasher = DefaultHasher::new(); @@ -226,13 +240,13 @@ macro_rules! rng_integer { }; let low = match range.start_bound() { - Bound::Unbounded => std::$t::MIN, + Bound::Unbounded => core::$t::MIN, Bound::Included(&x) => x, Bound::Excluded(&x) => x.checked_add(1).unwrap_or_else(panic_empty_range), }; let high = match range.end_bound() { - Bound::Unbounded => std::$t::MAX, + Bound::Unbounded => core::$t::MAX, Bound::Included(&x) => x, Bound::Excluded(&x) => x.checked_sub(1).unwrap_or_else(panic_empty_range), }; @@ -241,7 +255,7 @@ macro_rules! rng_integer { panic_empty_range(); } - if low == std::$t::MIN && high == std::$t::MAX { + if low == core::$t::MIN && high == core::$t::MAX { self.$gen() as $t } else { let len = high.wrapping_sub(low).wrapping_add(1); @@ -253,6 +267,7 @@ macro_rules! rng_integer { impl Rng { /// Creates a new random number generator. + #[cfg(feature = "std")] #[inline] pub fn new() -> Rng { try_with_rng(Rng::fork).unwrap_or_else(|_| Rng::with_seed(0x4d595df4d0f33173)) @@ -342,14 +357,14 @@ impl Rng { /// Generates a random `f32` in range `0..1`. pub fn f32(&mut self) -> f32 { let b = 32; - let f = std::f32::MANTISSA_DIGITS - 1; + let f = core::f32::MANTISSA_DIGITS - 1; f32::from_bits((1 << (b - 2)) - (1 << f) + (self.u32(..) >> (b - f))) - 1.0 } /// Generates a random `f64` in range `0..1`. pub fn f64(&mut self) -> f64 { let b = 64; - let f = std::f64::MANTISSA_DIGITS - 1; + let f = core::f64::MANTISSA_DIGITS - 1; f64::from_bits((1 << (b - 2)) - (1 << f) + (self.u64(..) >> (b - f))) - 1.0 } @@ -558,8 +573,6 @@ impl Rng { /// Panics if the range is empty. #[inline] pub fn char(&mut self, range: impl RangeBounds) -> char { - use std::convert::TryFrom; - let panic_empty_range = || { panic!( "empty range: {:?}..{:?}", @@ -585,7 +598,7 @@ impl Rng { }; let high = match range.end_bound() { - Bound::Unbounded => std::char::MAX, + Bound::Unbounded => core::char::MAX, Bound::Included(&x) => x, Bound::Excluded(&x) => { let scalar = if x as u32 == surrogate_start + surrogate_len { @@ -616,6 +629,7 @@ impl Rng { } /// Run an operation with the current thread-local generator. +#[cfg(feature = "std")] #[inline] fn with_rng(f: impl FnOnce(&mut Rng) -> R) -> R { RNG.with(|rng| { @@ -640,11 +654,13 @@ fn try_with_rng(f: impl FnOnce(&mut Rng) -> R) -> Result { rng: &'a Cell, current: Rng, } +#[cfg(feature = "std")] impl Drop for RestoreOnDrop<'_> { fn drop(&mut self) { self.rng.set(Rng(self.current.0)); @@ -652,42 +668,49 @@ impl Drop for RestoreOnDrop<'_> { } /// Initializes the thread-local generator with the given seed. +#[cfg(feature = "std")] #[inline] pub fn seed(seed: u64) { with_rng(|r| r.seed(seed)); } /// Gives back **current** seed that is being held by the thread-local generator. +#[cfg(feature = "std")] #[inline] pub fn get_seed() -> u64 { with_rng(|r| r.get_seed()) } /// Generates a random `bool`. +#[cfg(feature = "std")] #[inline] pub fn bool() -> bool { with_rng(|r| r.bool()) } /// Generates a random `char` in ranges a-z and A-Z. +#[cfg(feature = "std")] #[inline] pub fn alphabetic() -> char { with_rng(|r| r.alphabetic()) } /// Generates a random `char` in ranges a-z, A-Z and 0-9. +#[cfg(feature = "std")] #[inline] pub fn alphanumeric() -> char { with_rng(|r| r.alphanumeric()) } /// Generates a random `char` in range a-z. +#[cfg(feature = "std")] #[inline] pub fn lowercase() -> char { with_rng(|r| r.lowercase()) } /// Generates a random `char` in range A-Z. +#[cfg(feature = "std")] #[inline] pub fn uppercase() -> char { with_rng(|r| r.uppercase()) @@ -698,12 +721,14 @@ pub fn uppercase() -> char { /// Digits are represented by `char`s in ranges 0-9 and a-z. /// /// Panics if the base is zero or greater than 36. +#[cfg(feature = "std")] #[inline] pub fn digit(base: u32) -> char { with_rng(|r| r.digit(base)) } /// Shuffles a slice randomly. +#[cfg(feature = "std")] #[inline] pub fn shuffle(slice: &mut [T]) { with_rng(|r| r.shuffle(slice)) @@ -714,6 +739,7 @@ macro_rules! integer { #[doc = $doc] /// /// Panics if the range is empty. + #[cfg(feature = "std")] #[inline] pub fn $t(range: impl RangeBounds<$t>) -> $t { with_rng(|r| r.$t(range)) @@ -736,11 +762,13 @@ integer!(isize, "Generates a random `isize` in the given range."); integer!(char, "Generates a random `char` in the given range."); /// Generates a random `f32` in range `0..1`. +#[cfg(feature = "std")] pub fn f32() -> f32 { with_rng(|r| r.f32()) } /// Generates a random `f64` in range `0..1`. +#[cfg(feature = "std")] pub fn f64() -> f64 { with_rng(|r| r.f64()) } From 6455b1a3a1bbb875b618773ed58c4665cf9a3d12 Mon Sep 17 00:00:00 2001 From: notgull Date: Sat, 18 Feb 2023 12:06:54 -0800 Subject: [PATCH 2/2] move thread-locals to separate module --- src/global_rng.rs | 173 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 189 ++-------------------------------------------- 2 files changed, 178 insertions(+), 184 deletions(-) create mode 100644 src/global_rng.rs diff --git a/src/global_rng.rs b/src/global_rng.rs new file mode 100644 index 0000000..0c960ea --- /dev/null +++ b/src/global_rng.rs @@ -0,0 +1,173 @@ +//! A global, thread-local random number generator. + +use crate::Rng; + +use std::cell::Cell; +use std::ops::RangeBounds; + +#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))] +use instant::Instant; +#[cfg(not(all(target_arch = "wasm32", not(target_os = "wasi"))))] +use std::time::Instant; + +impl Default for Rng { + /// Initialize the `Rng` from the system's random number generator. + /// + /// This is equivalent to [`Rng::new()`]. + #[inline] + fn default() -> Rng { + Rng::new() + } +} + +impl Rng { + /// Creates a new random number generator. + #[inline] + pub fn new() -> Rng { + try_with_rng(Rng::fork).unwrap_or_else(|_| Rng::with_seed(0x4d595df4d0f33173)) + } +} + +thread_local! { + static RNG: Cell = Cell::new(Rng({ + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + use std::thread; + + let mut hasher = DefaultHasher::new(); + Instant::now().hash(&mut hasher); + thread::current().id().hash(&mut hasher); + let hash = hasher.finish(); + (hash << 1) | 1 + })); +} + +/// Run an operation with the current thread-local generator. +#[inline] +fn with_rng(f: impl FnOnce(&mut Rng) -> R) -> R { + RNG.with(|rng| { + let current = rng.replace(Rng(0)); + + let mut restore = RestoreOnDrop { rng, current }; + + f(&mut restore.current) + }) +} + +/// Try to run an operation with the current thread-local generator. +#[inline] +fn try_with_rng(f: impl FnOnce(&mut Rng) -> R) -> Result { + RNG.try_with(|rng| { + let current = rng.replace(Rng(0)); + + let mut restore = RestoreOnDrop { rng, current }; + + f(&mut restore.current) + }) +} + +/// Make sure the original RNG is restored even on panic. +struct RestoreOnDrop<'a> { + rng: &'a Cell, + current: Rng, +} + +impl Drop for RestoreOnDrop<'_> { + fn drop(&mut self) { + self.rng.set(Rng(self.current.0)); + } +} + +/// Initializes the thread-local generator with the given seed. +#[inline] +pub fn seed(seed: u64) { + with_rng(|r| r.seed(seed)); +} + +/// Gives back **current** seed that is being held by the thread-local generator. +#[inline] +pub fn get_seed() -> u64 { + with_rng(|r| r.get_seed()) +} + +/// Generates a random `bool`. +#[inline] +pub fn bool() -> bool { + with_rng(|r| r.bool()) +} + +/// Generates a random `char` in ranges a-z and A-Z. +#[inline] +pub fn alphabetic() -> char { + with_rng(|r| r.alphabetic()) +} + +/// Generates a random `char` in ranges a-z, A-Z and 0-9. +#[inline] +pub fn alphanumeric() -> char { + with_rng(|r| r.alphanumeric()) +} + +/// Generates a random `char` in range a-z. +#[inline] +pub fn lowercase() -> char { + with_rng(|r| r.lowercase()) +} + +/// Generates a random `char` in range A-Z. +#[inline] +pub fn uppercase() -> char { + with_rng(|r| r.uppercase()) +} + +/// Generates a random digit in the given `base`. +/// +/// Digits are represented by `char`s in ranges 0-9 and a-z. +/// +/// Panics if the base is zero or greater than 36. +#[inline] +pub fn digit(base: u32) -> char { + with_rng(|r| r.digit(base)) +} + +/// Shuffles a slice randomly. +#[inline] +pub fn shuffle(slice: &mut [T]) { + with_rng(|r| r.shuffle(slice)) +} + +macro_rules! integer { + ($t:tt, $doc:tt) => { + #[doc = $doc] + /// + /// Panics if the range is empty. + #[inline] + pub fn $t(range: impl RangeBounds<$t>) -> $t { + with_rng(|r| r.$t(range)) + } + }; +} + +integer!(u8, "Generates a random `u8` in the given range."); +integer!(i8, "Generates a random `i8` in the given range."); +integer!(u16, "Generates a random `u16` in the given range."); +integer!(i16, "Generates a random `i16` in the given range."); +integer!(u32, "Generates a random `u32` in the given range."); +integer!(i32, "Generates a random `i32` in the given range."); +integer!(u64, "Generates a random `u64` in the given range."); +integer!(i64, "Generates a random `i64` in the given range."); +integer!(u128, "Generates a random `u128` in the given range."); +integer!(i128, "Generates a random `i128` in the given range."); +integer!(usize, "Generates a random `usize` in the given range."); +integer!(isize, "Generates a random `isize` in the given range."); +integer!(char, "Generates a random `char` in the given range."); + +/// Generates a random `f32` in range `0..1`. +pub fn f32() -> f32 { + with_rng(|r| r.f32()) +} + +/// Generates a random `f64` in range `0..1`. +pub fn f64() -> f64 { + with_rng(|r| r.f64()) +} diff --git a/src/lib.rs b/src/lib.rs index fe1d279..c47160b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,9 +64,9 @@ //! let mut rng = fastrand::Rng::new(); //! let mut bytes: Vec = repeat_with(|| rng.u8(..)).take(10_000).collect(); //! ``` -//! +//! //! # Features -//! +//! //! - `std` (enabled by default): Enables the `std` library. This is required for the global //! generator and global entropy. Without this feature, [`Rng`] can only be instantiated using //! the [`with_seed`](Rng::with_seed) method. @@ -79,31 +79,15 @@ use core::convert::{TryFrom, TryInto}; use core::ops::{Bound, RangeBounds}; #[cfg(feature = "std")] -use std::cell::Cell; -#[cfg(feature = "std")] -use std::collections::hash_map::DefaultHasher; -#[cfg(feature = "std")] -use std::hash::{Hash, Hasher}; -#[cfg(feature = "std")] -use std::thread; +mod global_rng; -#[cfg(all(feature = "std", target_arch = "wasm32", not(target_os = "wasi")))] -use instant::Instant; -#[cfg(all(feature = "std", not(all(target_arch = "wasm32", not(target_os = "wasi")))))] -use std::time::Instant; +#[cfg(feature = "std")] +pub use global_rng::*; /// A random number generator. #[derive(Debug, PartialEq, Eq)] pub struct Rng(u64); -#[cfg(feature = "std")] -impl Default for Rng { - #[inline] - fn default() -> Rng { - Rng::new() - } -} - impl Clone for Rng { /// Clones the generator by creating a new generator with the same seed. fn clone(&self) -> Rng { @@ -188,17 +172,6 @@ impl Rng { } } -#[cfg(feature = "std")] -thread_local! { - static RNG: Cell = Cell::new(Rng({ - let mut hasher = DefaultHasher::new(); - Instant::now().hash(&mut hasher); - thread::current().id().hash(&mut hasher); - let hash = hasher.finish(); - (hash << 1) | 1 - })); -} - /// Computes `(a * b) >> 32`. #[inline] fn mul_high_u32(a: u32, b: u32) -> u32 { @@ -266,13 +239,6 @@ macro_rules! rng_integer { } impl Rng { - /// Creates a new random number generator. - #[cfg(feature = "std")] - #[inline] - pub fn new() -> Rng { - try_with_rng(Rng::fork).unwrap_or_else(|_| Rng::with_seed(0x4d595df4d0f33173)) - } - /// Creates a new random number generator with the initial seed. #[inline] #[must_use = "this creates a new instance of `Rng`; if you want to initialize the thread-local generator, use `fastrand::seed()` instead"] @@ -627,148 +593,3 @@ impl Rng { val.try_into().unwrap() } } - -/// Run an operation with the current thread-local generator. -#[cfg(feature = "std")] -#[inline] -fn with_rng(f: impl FnOnce(&mut Rng) -> R) -> R { - RNG.with(|rng| { - let current = rng.replace(Rng(0)); - - let mut restore = RestoreOnDrop { rng, current }; - - f(&mut restore.current) - }) -} - -/// Try to run an operation with the current thread-local generator. -#[inline] -fn try_with_rng(f: impl FnOnce(&mut Rng) -> R) -> Result { - RNG.try_with(|rng| { - let current = rng.replace(Rng(0)); - - let mut restore = RestoreOnDrop { rng, current }; - - f(&mut restore.current) - }) -} - -/// Make sure the original RNG is restored even on panic. -#[cfg(feature = "std")] -struct RestoreOnDrop<'a> { - rng: &'a Cell, - current: Rng, -} - -#[cfg(feature = "std")] -impl Drop for RestoreOnDrop<'_> { - fn drop(&mut self) { - self.rng.set(Rng(self.current.0)); - } -} - -/// Initializes the thread-local generator with the given seed. -#[cfg(feature = "std")] -#[inline] -pub fn seed(seed: u64) { - with_rng(|r| r.seed(seed)); -} - -/// Gives back **current** seed that is being held by the thread-local generator. -#[cfg(feature = "std")] -#[inline] -pub fn get_seed() -> u64 { - with_rng(|r| r.get_seed()) -} - -/// Generates a random `bool`. -#[cfg(feature = "std")] -#[inline] -pub fn bool() -> bool { - with_rng(|r| r.bool()) -} - -/// Generates a random `char` in ranges a-z and A-Z. -#[cfg(feature = "std")] -#[inline] -pub fn alphabetic() -> char { - with_rng(|r| r.alphabetic()) -} - -/// Generates a random `char` in ranges a-z, A-Z and 0-9. -#[cfg(feature = "std")] -#[inline] -pub fn alphanumeric() -> char { - with_rng(|r| r.alphanumeric()) -} - -/// Generates a random `char` in range a-z. -#[cfg(feature = "std")] -#[inline] -pub fn lowercase() -> char { - with_rng(|r| r.lowercase()) -} - -/// Generates a random `char` in range A-Z. -#[cfg(feature = "std")] -#[inline] -pub fn uppercase() -> char { - with_rng(|r| r.uppercase()) -} - -/// Generates a random digit in the given `base`. -/// -/// Digits are represented by `char`s in ranges 0-9 and a-z. -/// -/// Panics if the base is zero or greater than 36. -#[cfg(feature = "std")] -#[inline] -pub fn digit(base: u32) -> char { - with_rng(|r| r.digit(base)) -} - -/// Shuffles a slice randomly. -#[cfg(feature = "std")] -#[inline] -pub fn shuffle(slice: &mut [T]) { - with_rng(|r| r.shuffle(slice)) -} - -macro_rules! integer { - ($t:tt, $doc:tt) => { - #[doc = $doc] - /// - /// Panics if the range is empty. - #[cfg(feature = "std")] - #[inline] - pub fn $t(range: impl RangeBounds<$t>) -> $t { - with_rng(|r| r.$t(range)) - } - }; -} - -integer!(u8, "Generates a random `u8` in the given range."); -integer!(i8, "Generates a random `i8` in the given range."); -integer!(u16, "Generates a random `u16` in the given range."); -integer!(i16, "Generates a random `i16` in the given range."); -integer!(u32, "Generates a random `u32` in the given range."); -integer!(i32, "Generates a random `i32` in the given range."); -integer!(u64, "Generates a random `u64` in the given range."); -integer!(i64, "Generates a random `i64` in the given range."); -integer!(u128, "Generates a random `u128` in the given range."); -integer!(i128, "Generates a random `i128` in the given range."); -integer!(usize, "Generates a random `usize` in the given range."); -integer!(isize, "Generates a random `isize` in the given range."); -integer!(char, "Generates a random `char` in the given range."); - -/// Generates a random `f32` in range `0..1`. -#[cfg(feature = "std")] -pub fn f32() -> f32 { - with_rng(|r| r.f32()) -} - -/// Generates a random `f64` in range `0..1`. -#[cfg(feature = "std")] -pub fn f64() -> f64 { - with_rng(|r| r.f64()) -}