Skip to content

Commit

Permalink
Add no_std mode
Browse files Browse the repository at this point in the history
  • Loading branch information
notgull committed Feb 13, 2023
1 parent 0e0209a commit e6fa33f
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 13 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
54 changes: 41 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,26 +64,39 @@
//! let mut rng = fastrand::Rng::new();
//! let mut bytes: Vec<u8> = 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 {
Expand Down Expand Up @@ -175,6 +188,7 @@ impl Rng {
}
}

#[cfg(feature = "std")]
thread_local! {
static RNG: Cell<Rng> = Cell::new(Rng({
let mut hasher = DefaultHasher::new();
Expand Down Expand Up @@ -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),
};
Expand All @@ -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);
Expand All @@ -253,6 +267,7 @@ macro_rules! rng_integer {

impl Rng {
/// Creates a new random number generator.
#[cfg(feature = "std")]
#[inline]
pub fn new() -> Rng {
Rng::with_seed({
Expand Down Expand Up @@ -324,14 +339,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
}

Expand Down Expand Up @@ -540,8 +555,6 @@ impl Rng {
/// Panics if the range is empty.
#[inline]
pub fn char(&mut self, range: impl RangeBounds<char>) -> char {
use std::convert::TryFrom;

let panic_empty_range = || {
panic!(
"empty range: {:?}..{:?}",
Expand All @@ -567,7 +580,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 {
Expand Down Expand Up @@ -598,6 +611,7 @@ impl Rng {
}

/// Run an operation with the current thread-local generator.
#[cfg(feature = "std")]
#[inline]
fn with_rng<R>(f: impl FnOnce(&mut Rng) -> R) -> R {
RNG.with(|rng| {
Expand All @@ -610,54 +624,63 @@ fn with_rng<R>(f: impl FnOnce(&mut Rng) -> R) -> R {
}

/// Make sure the original RNG is restored even on panic.
#[cfg(feature = "std")]
struct RestoreOnDrop<'a> {
rng: &'a Cell<Rng>,
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())
Expand All @@ -668,12 +691,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<T>(slice: &mut [T]) {
with_rng(|r| r.shuffle(slice))
Expand All @@ -684,6 +709,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))
Expand All @@ -706,11 +732,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())
}

0 comments on commit e6fa33f

Please sign in to comment.