diff --git a/Cargo.toml b/Cargo.toml index 57b2f5d3..b808deae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,11 +14,16 @@ keywords = ["random", "rng"] categories = ["algorithms"] [features] -i128_support = [] -nightly = ["i128_support"] +default = ["std"] +nightly = ["i128_support"] # enables all features requiring nightly rust + +std = ["libc"] # default feature; without this rand uses libcore +alloc = [] # enables Vec and Box support without std + +i128_support = [] # enables i128 and u128 support [dependencies] -libc = "0.2" +libc = { version = "0.2", optional = true } [dev-dependencies] log = "0.3.0" diff --git a/README.md b/README.md index f19845a9..a36c3cd1 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,48 @@ let mut rng = rand::ChaChaRng::new_unseeded(); println!("i32: {}, u32: {}", rng.gen::(), rng.gen::()) ``` +## Features + +By default, `rand` is built with all stable features available. The following +optional features are available: + +- `i128_support` enables support for generating `u128` and `i128` values +- `nightly` enables all unstable features (`i128_support`) +- `std` enabled by default; by setting "default-features = false" `no_std` + mode is activated; this removes features depending on `std` functionality: + + - `OsRng` is entirely unavailable + - `JitterRng` code is still present, but a nanosecond timer must be + provided via `JitterRng::new_with_timer` + - Since no external entropy is available, it is not possible to create + generators with fresh seeds (user must provide entropy) + - `thread_rng`, `weak_rng` and `random` are all disabled + - exponential, normal and gamma type distributions are unavailable + since `exp` and `log` functions are not provided in `core` + - any code requiring `Vec` or `Box` +- `alloc` can be used instead of `std` to provide `Vec` and `Box` + +## Testing + +Unfortunately, `cargo test` does not test everything. The following tests are +recommended: + +``` +# Basic tests for rand and sub-crates +cargo test --all + +# Test no_std support (build only since nearly all tests require std) +cargo build --all --no-default-features + +# Test 128-bit support (requires nightly) +cargo test --all --features nightly + +# Benchmarks (requires nightly) +cargo bench +# or just to test the benchmark code: +cargo test --benches +``` + # `derive(Rand)` You can derive the `Rand` trait for your custom type via the `#[derive(Rand)]` diff --git a/src/distributions/mod.rs b/src/distributions/mod.rs index 876735aa..63856a51 100644 --- a/src/distributions/mod.rs +++ b/src/distributions/mod.rs @@ -17,20 +17,29 @@ //! internally. The `IndependentSample` trait is for generating values //! that do not need to record state. -use std::marker; +use core::marker; use {Rng, Rand}; pub use self::range::Range; +#[cfg(feature="std")] pub use self::gamma::{Gamma, ChiSquared, FisherF, StudentT}; +#[cfg(feature="std")] pub use self::normal::{Normal, LogNormal}; +#[cfg(feature="std")] pub use self::exponential::Exp; pub mod range; +#[cfg(feature="std")] pub mod gamma; +#[cfg(feature="std")] pub mod normal; +#[cfg(feature="std")] pub mod exponential; +#[cfg(feature="std")] +mod ziggurat_tables; + /// Types that can be used to create a random instance of `Support`. pub trait Sample { /// Generate a random value of `Support`, using `rng` as the @@ -203,8 +212,6 @@ impl<'a, T: Clone> IndependentSample for WeightedChoice<'a, T> { } } -mod ziggurat_tables; - /// Sample a random number using the Ziggurat method (specifically the /// ZIGNOR variant from Doornik 2005). Most of the arguments are /// directly from the paper: @@ -220,6 +227,7 @@ mod ziggurat_tables; // the perf improvement (25-50%) is definitely worth the extra code // size from force-inlining. +#[cfg(feature="std")] #[inline(always)] fn ziggurat( rng: &mut R, diff --git a/src/distributions/range.rs b/src/distributions/range.rs index 4d11e807..935a00aa 100644 --- a/src/distributions/range.rs +++ b/src/distributions/range.rs @@ -12,7 +12,7 @@ // this is surprisingly complicated to be both generic & correct -use std::num::Wrapping as w; +use core::num::Wrapping as w; use Rng; use distributions::{Sample, IndependentSample}; @@ -99,7 +99,7 @@ macro_rules! integer_impl { #[inline] fn construct_range(low: $ty, high: $ty) -> Range<$ty> { let range = (w(high as $unsigned) - w(low as $unsigned)).0; - let unsigned_max: $unsigned = ::std::$unsigned::MAX; + let unsigned_max: $unsigned = ::core::$unsigned::MAX; // this is the largest number that fits into $unsigned // that `range` divides evenly, so, if we've sampled @@ -191,7 +191,7 @@ mod tests { $( let v: &[($ty, $ty)] = &[(0, 10), (10, 127), - (::std::$ty::MIN, ::std::$ty::MAX)]; + (::core::$ty::MIN, ::core::$ty::MAX)]; for &(low, high) in v.iter() { let mut sampler: Range<$ty> = Range::new(low, high); for _ in 0..1000 { diff --git a/src/jitter.rs b/src/jitter.rs index aeb9fd5d..4eb53581 100644 --- a/src/jitter.rs +++ b/src/jitter.rs @@ -18,7 +18,8 @@ use Rng; -use std::{fmt, mem, ptr}; +use core::{fmt, mem, ptr}; +#[cfg(feature="std")] use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; const MEMORY_BLOCKS: usize = 64; @@ -107,6 +108,7 @@ impl fmt::Display for TimerError { } } +#[cfg(feature="std")] impl ::std::error::Error for TimerError { fn description(&self) -> &str { self.description() @@ -114,6 +116,7 @@ impl ::std::error::Error for TimerError { } // Initialise to zero; must be positive +#[cfg(feature="std")] static JITTER_ROUNDS: AtomicUsize = ATOMIC_USIZE_INIT; impl JitterRng { @@ -123,8 +126,9 @@ impl JitterRng { /// During initialization CPU execution timing jitter is measured a few /// hundred times. If this does not pass basic quality tests, an error is /// returned. The test result is cached to make subsequent calls faster. + #[cfg(feature="std")] pub fn new() -> Result { - let mut ec = JitterRng::new_with_timer(get_nstime); + let mut ec = JitterRng::new_with_timer(platform::get_nstime); let mut rounds = JITTER_ROUNDS.load(Ordering::Relaxed) as u32; if rounds == 0 { // No result yet: run test. @@ -648,57 +652,61 @@ impl JitterRng { /// # try_main().unwrap(); /// # } /// ``` + #[cfg(feature="std")] pub fn timer_stats(&mut self, var_rounds: bool) -> i64 { - let time = get_nstime(); + let time = platform::get_nstime(); self.memaccess(var_rounds); self.lfsr_time(time, var_rounds); - let time2 = get_nstime(); + let time2 = platform::get_nstime(); time2.wrapping_sub(time) as i64 } } -#[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows", all(target_arch = "wasm32", not(target_os = "emscripten")))))] -fn get_nstime() -> u64 { - use std::time::{SystemTime, UNIX_EPOCH}; +#[cfg(feature="std")] +mod platform { + #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "windows", all(target_arch = "wasm32", not(target_os = "emscripten")))))] + pub fn get_nstime() -> u64 { + use std::time::{SystemTime, UNIX_EPOCH}; - let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - // The correct way to calculate the current time is - // `dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64` - // But this is faster, and the difference in terms of entropy is negligible - // (log2(10^9) == 29.9). - dur.as_secs() << 30 | dur.subsec_nanos() as u64 -} - -#[cfg(any(target_os = "macos", target_os = "ios"))] -fn get_nstime() -> u64 { - extern crate libc; - // On Mac OS and iOS std::time::SystemTime only has 1000ns resolution. - // We use `mach_absolute_time` instead. This provides a CPU dependent unit, - // to get real nanoseconds the result should by multiplied by numer/denom - // from `mach_timebase_info`. - // But we are not interested in the exact nanoseconds, just entropy. So we - // use the raw result. - unsafe { libc::mach_absolute_time() } -} - -#[cfg(target_os = "windows")] -fn get_nstime() -> u64 { - #[allow(non_camel_case_types)] - type LARGE_INTEGER = i64; - #[allow(non_camel_case_types)] - type BOOL = i32; - extern "system" { - fn QueryPerformanceCounter(lpPerformanceCount: *mut LARGE_INTEGER) -> BOOL; + let dur = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + // The correct way to calculate the current time is + // `dur.as_secs() * 1_000_000_000 + dur.subsec_nanos() as u64` + // But this is faster, and the difference in terms of entropy is negligible + // (log2(10^9) == 29.9). + dur.as_secs() << 30 | dur.subsec_nanos() as u64 } - let mut t = 0; - unsafe { QueryPerformanceCounter(&mut t); } - t as u64 -} + #[cfg(any(target_os = "macos", target_os = "ios"))] + pub fn get_nstime() -> u64 { + extern crate libc; + // On Mac OS and iOS std::time::SystemTime only has 1000ns resolution. + // We use `mach_absolute_time` instead. This provides a CPU dependent unit, + // to get real nanoseconds the result should by multiplied by numer/denom + // from `mach_timebase_info`. + // But we are not interested in the exact nanoseconds, just entropy. So we + // use the raw result. + unsafe { libc::mach_absolute_time() } + } -#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] -fn get_nstime() -> u64 { - unreachable!() + #[cfg(target_os = "windows")] + pub fn get_nstime() -> u64 { + #[allow(non_camel_case_types)] + type LARGE_INTEGER = i64; + #[allow(non_camel_case_types)] + type BOOL = i32; + extern "system" { + fn QueryPerformanceCounter(lpPerformanceCount: *mut LARGE_INTEGER) -> BOOL; + } + + let mut t = 0; + unsafe { QueryPerformanceCounter(&mut t); } + t as u64 + } + + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + pub fn get_nstime() -> u64 { + unreachable!() + } } // A function that is opaque to the optimizer to assist in avoiding dead-code diff --git a/src/lib.rs b/src/lib.rs index 05542240..74fdbe34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -243,19 +243,22 @@ #![deny(missing_debug_implementations)] +#![cfg_attr(not(feature="std"), no_std)] +#![cfg_attr(all(feature="alloc", not(feature="std")), feature(alloc))] #![cfg_attr(feature = "i128_support", feature(i128_type, i128))] +#[cfg(feature="std")] extern crate std as core; +#[cfg(all(feature = "alloc", not(feature="std")))] extern crate alloc; #[cfg(test)] #[macro_use] extern crate log; - -use std::cell::RefCell; -use std::marker; -use std::mem; -use std::io; -use std::rc::Rc; +use core::marker; +use core::mem; +#[cfg(feature="std")] use std::cell::RefCell; +#[cfg(feature="std")] use std::io; +#[cfg(feature="std")] use std::rc::Rc; pub use jitter::JitterRng; -pub use os::OsRng; +#[cfg(feature="std")] pub use os::OsRng; pub use isaac::{IsaacRng, Isaac64Rng}; pub use chacha::ChaChaRng; @@ -274,9 +277,9 @@ pub mod distributions; pub mod reseeding; mod rand_impls; pub mod jitter; -pub mod os; -pub mod read; -pub mod seq; +#[cfg(feature="std")] pub mod os; +#[cfg(feature="std")] pub mod read; +#[cfg(any(feature="std", feature = "alloc"))] pub mod seq; mod prng; // These tiny modules are here to avoid API breakage, probably only temporarily @@ -625,6 +628,7 @@ impl<'a, R: ?Sized> Rng for &'a mut R where R: Rng { } } +#[cfg(feature="std")] impl Rng for Box where R: Rng { fn next_u32(&mut self) -> u32 { (**self).next_u32() @@ -776,6 +780,7 @@ impl StdRng { /// /// Reading the randomness from the OS may fail, and any error is /// propagated via the `io::Result` return value. + #[cfg(feature="std")] pub fn new() -> io::Result { match OsRng::new() { Ok(mut r) => Ok(StdRng { rng: r.gen() }), @@ -823,14 +828,17 @@ impl<'a> SeedableRng<&'a [usize]> for StdRng { /// create the `Rng` yourself. /// /// This will seed the generator with randomness from thread_rng. +#[cfg(feature="std")] pub fn weak_rng() -> XorShiftRng { thread_rng().gen() } /// Controls how the thread-local RNG is reseeded. +#[cfg(feature="std")] #[derive(Debug)] struct ThreadRngReseeder; +#[cfg(feature="std")] impl reseeding::Reseeder for ThreadRngReseeder { fn reseed(&mut self, rng: &mut StdRng) { match StdRng::new() { @@ -839,10 +847,13 @@ impl reseeding::Reseeder for ThreadRngReseeder { } } } +#[cfg(feature="std")] const THREAD_RNG_RESEED_THRESHOLD: u64 = 32_768; +#[cfg(feature="std")] type ThreadRngInner = reseeding::ReseedingRng; /// The thread-local RNG. +#[cfg(feature="std")] #[derive(Clone, Debug)] pub struct ThreadRng { rng: Rc>, @@ -860,6 +871,7 @@ pub struct ThreadRng { /// if the operating system random number generator is rigged to give /// the same sequence always. If absolute consistency is required, /// explicitly select an RNG, e.g. `IsaacRng` or `Isaac64Rng`. +#[cfg(feature="std")] pub fn thread_rng() -> ThreadRng { // used to make space in TLS for a random number generator thread_local!(static THREAD_RNG_KEY: Rc> = { @@ -876,6 +888,7 @@ pub fn thread_rng() -> ThreadRng { ThreadRng { rng: THREAD_RNG_KEY.with(|t| t.clone()) } } +#[cfg(feature="std")] impl Rng for ThreadRng { fn next_u32(&mut self) -> u32 { self.rng.borrow_mut().next_u32() @@ -933,13 +946,12 @@ impl Rng for ThreadRng { /// *x = rng.gen(); /// } /// ``` +#[cfg(feature="std")] #[inline] pub fn random() -> T { thread_rng().gen() } -#[inline(always)] -#[deprecated(since="0.4.0", note="renamed to seq::sample_iter")] /// DEPRECATED: use `seq::sample_iter` instead. /// /// Randomly sample up to `amount` elements from a finite iterator. @@ -954,6 +966,9 @@ pub fn random() -> T { /// let sample = sample(&mut rng, 1..100, 5); /// println!("{:?}", sample); /// ``` +#[cfg(feature="std")] +#[inline(always)] +#[deprecated(since="0.4.0", note="renamed to seq::sample_iter")] pub fn sample(rng: &mut R, iterable: I, amount: usize) -> Vec where I: IntoIterator, R: Rng, diff --git a/src/prng/chacha.rs b/src/prng/chacha.rs index c11b3076..a73e8e78 100644 --- a/src/prng/chacha.rs +++ b/src/prng/chacha.rs @@ -10,7 +10,7 @@ //! The ChaCha random number generator. -use std::num::Wrapping as w; +use core::num::Wrapping as w; use {Rng, SeedableRng, Rand}; #[allow(bad_style)] diff --git a/src/prng/isaac.rs b/src/prng/isaac.rs index a9ae30e4..e7787961 100644 --- a/src/prng/isaac.rs +++ b/src/prng/isaac.rs @@ -12,10 +12,10 @@ #![allow(non_camel_case_types)] -use std::slice; -use std::iter::repeat; -use std::num::Wrapping as w; -use std::fmt; +use core::slice; +use core::iter::repeat; +use core::num::Wrapping as w; +use core::fmt; use {Rng, SeedableRng, Rand}; diff --git a/src/prng/xorshift.rs b/src/prng/xorshift.rs index a8c3e667..dd367e9b 100644 --- a/src/prng/xorshift.rs +++ b/src/prng/xorshift.rs @@ -10,7 +10,7 @@ //! Xorshift generators -use std::num::Wrapping as w; +use core::num::Wrapping as w; use {Rng, SeedableRng, Rand}; /// An Xorshift[1] random number diff --git a/src/rand_impls.rs b/src/rand_impls.rs index a9cf5d99..a865bb69 100644 --- a/src/rand_impls.rs +++ b/src/rand_impls.rs @@ -10,8 +10,7 @@ //! The implementations of `Rand` for the built-in types. -use std::char; -use std::mem; +use core::{char, mem}; use {Rand,Rng}; diff --git a/src/reseeding.rs b/src/reseeding.rs index 2fba9f4a..1f24e200 100644 --- a/src/reseeding.rs +++ b/src/reseeding.rs @@ -11,7 +11,7 @@ //! A wrapper around another RNG that reseeds it after it //! generates a certain number of random bytes. -use std::default::Default; +use core::default::Default; use {Rng, SeedableRng}; diff --git a/src/seq.rs b/src/seq.rs index 25a62497..a7889fe3 100644 --- a/src/seq.rs +++ b/src/seq.rs @@ -11,7 +11,13 @@ //! Functions for randomly accessing and sampling sequences. use super::Rng; -use std::collections::hash_map::HashMap; + +// This crate is only enabled when either std or alloc is available. +// BTreeMap is not as fast in tests, but better than nothing. +#[cfg(feature="std")] use std::collections::HashMap; +#[cfg(not(feature="std"))] use alloc::btree_map::BTreeMap; + +#[cfg(not(feature="std"))] use alloc::Vec; /// Randomly sample `amount` elements from a finite iterator. /// @@ -189,7 +195,8 @@ fn sample_indices_cache( where R: Rng, { debug_assert!(amount <= length); - let mut cache = HashMap::with_capacity(amount); + #[cfg(feature="std")] let mut cache = HashMap::with_capacity(amount); + #[cfg(not(feature="std"))] let mut cache = BTreeMap::new(); let mut out = Vec::with_capacity(amount); for i in 0..amount { let j: usize = rng.gen_range(i, length);