Skip to content

Commit 3b0042a

Browse files
committed
feat(rand): adds BufferedRng
1 parent 364ff78 commit 3b0042a

File tree

4 files changed

+137
-9
lines changed

4 files changed

+137
-9
lines changed

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ license = "Zlib"
1212

1313
[features]
1414
default = ["std", "tls", "wyrand", "pcg64", "chacha"]
15-
std = []
15+
alloc = []
16+
std = ["alloc"]
1617
tls = ["std", "wyrand"]
1718
wyrand = []
1819
pcg64 = []

README.md

+18-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# nanorand
44

5-
Current version: **0.6.1**
5+
Current version: **0.7.0**
66

77
A library meant for fast, random number generation with quick compile time, and minimal dependencies.
88

@@ -29,6 +29,17 @@ let mut rng = WyRand::new();
2929
println!("Random number between 1 and 100: {}", rng.generate_range(1_u64..=100));
3030
println!("Random number between -100 and 50: {}", rng.generate_range(-100_i64..=50));
3131
```
32+
#### Buffering random bytes
33+
```rust
34+
use nanorand::{Rng, BufferedRng, WyRand};
35+
36+
let mut thingy = [0u8; 5];
37+
let mut rng = BufferedRng::new(WyRand::new());
38+
rng.fill(&mut thingy);
39+
// As WyRand generates 8 bytes of output, and our target is only 5 bytes,
40+
// 3 bytes will remain in the buffer.
41+
assert_eq!(rng.buffered(), 3);
42+
```
3243
### Shuffling a Vec
3344
```rust
3445
use nanorand::{Rng, WyRand};
@@ -48,9 +59,9 @@ rng.shuffle(&mut items);
4859

4960
**RNG**|**nanorand type**|**Output Size**|**Cryptographically Secure**|**Speed**<sup>1</sup>|**Notes**|**Original Implementation**
5061
:-----:|:-----:|:-----:|:-----:|:-----:|:-----:|:-----:
51-
wyrand|[`nanorand::WyRand`](rand/wyrand/struct.WyRand.html), [`nanorand::tls::TlsWyRand`](tls/fn.tls_rng.html)|64 bits (`u64`)|🚫|16.4 GB/s||[https://github.com/lemire/testingRNG/blob/master/source/wyrand.h](https://github.com/lemire/testingRNG/blob/master/source/wyrand.h)
52-
Pcg64|[`nanorand::Pcg64`](rand/pcg64/struct.Pcg64.html)|64 bits (`u64`)|🚫|1.6 GB/s||[https://github.com/rkern/pcg64](https://github.com/rkern/pcg64)
53-
ChaCha|[`nanorand::ChaCha`](rand/chacha/struct.ChaCha.html)|512 bits (`[u32; 16]`)||204 MB/s (ChaCha8), 79 MB/s (ChaCha20)|Only works in Rust 1.47 or above|[https://cr.yp.to/chacha.html](https://cr.yp.to/chacha.html)
62+
wyrand|[`nanorand::WyRand`](rand/wyrand/struct.WyRand.html), [`nanorand::tls::TlsWyRand`](tls/fn.tls_rng.html)|64 bits (`u64`)|🚫|16.4 GB/s||[https://github.com/lemire/testingRNG/blob/master/source/wyrand.h](https://github.com/lemire/testingRNG/blob/master/source/wyrand.h)
63+
Pcg64|[`nanorand::Pcg64`](rand/pcg64/struct.Pcg64.html)|64 bits (`u64`)|🚫|1.6 GB/s||[https://github.com/rkern/pcg64](https://github.com/rkern/pcg64)
64+
ChaCha|[`nanorand::ChaCha`](rand/chacha/struct.ChaCha.html)|512 bits (`[u32; 16]`)|✅|204 MB/s (ChaCha8), 79 MB/s (ChaCha20)|Only works in Rust 1.47 or above|[https://cr.yp.to/chacha.html](https://cr.yp.to/chacha.html)
5465

5566
<sup>1. Speed benchmarked on an M1 Macbook Air</sup>
5667

@@ -68,8 +79,9 @@ _Listed in order of priority_
6879

6980
### Feature Flags
7081

71-
* `std` (default) - Enables Rust `std` lib features, such as seeding from OS entropy sources.
72-
* `tls` (default) - Enables a thread-local [`WyRand`](rand/wyrand/struct.WyRand.html) RNG (see below). Requires `tls` to be enabled.
82+
* `alloc` (default) - Enables Rust `alloc` lib features, such as a buffering Rng wrapper.
83+
* `std` (default) - Enables Rust `std` lib features, such as seeding from OS entropy sources. Requires `alloc` to be enabled.
84+
* `tls` (default) - Enables a thread-local [`WyRand`](rand/wyrand/struct.WyRand.html) RNG (see below). Requires `std` to be enabled.
7385
* `wyrand` (default) - Enable the [`WyRand`](rand/wyrand/struct.WyRand.html) RNG.
7486
* `pcg64` (default) - Enable the [`Pcg64`](rand/pcg64/struct.Pcg64.html) RNG.
7587
* `chacha` - Enable the [`ChaCha`](rand/chacha/struct.ChaCha.html) RNG. Requires Rust 1.47 or later.

src/buffer.rs

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use crate::rand::Rng;
2+
use alloc::vec::Vec;
3+
use core::{
4+
default::Default,
5+
ops::{Deref, DerefMut},
6+
};
7+
8+
/// A buffered wrapper for any [Rng] implementation.
9+
/// It will keep unused bytes from the last call to [`Rng::rand`], and use them
10+
/// for subsequent randomness if needed, rather than throwing them away.
11+
///
12+
/// ```rust
13+
/// use nanorand::{Rng, BufferedRng, WyRand};
14+
///
15+
/// let mut thingy = [0u8; 5];
16+
/// let mut rng = BufferedRng::new(WyRand::new());
17+
/// rng.fill(&mut thingy);
18+
/// // As WyRand generates 8 bytes of output, and our target is only 5 bytes,
19+
/// // 3 bytes will remain in the buffer.
20+
/// assert_eq!(rng.buffered(), 3);
21+
/// ```
22+
#[derive(Clone)]
23+
pub struct BufferedRng<const OUTPUT: usize, R: Rng<OUTPUT>> {
24+
rng: R,
25+
buffer: Vec<u8>,
26+
}
27+
28+
impl<const OUTPUT: usize, R: Rng<OUTPUT>> BufferedRng<OUTPUT, R> {
29+
/// Wraps a [`Rng`] generator in a [`BufferedRng`] instance.
30+
pub fn new(rng: R) -> Self {
31+
Self {
32+
rng,
33+
buffer: Vec::new(),
34+
}
35+
}
36+
37+
/// Returns how many unused bytes are currently buffered.
38+
pub fn buffered(&self) -> usize {
39+
self.buffer.len()
40+
}
41+
42+
/// Fills the output with random bytes, first using bytes from the internal buffer,
43+
/// and then using the [`Rng`] generator, and discarding unused bytes into the buffer.
44+
pub fn fill(&mut self, output: &mut [u8]) {
45+
let mut remaining = output.len();
46+
while remaining > 0 {
47+
if self.buffer.is_empty() {
48+
self.buffer.extend_from_slice(&self.rng.rand());
49+
}
50+
let to_copy = core::cmp::min(remaining, self.buffer.len());
51+
let output_len = output.len();
52+
let start_idx = output_len - remaining;
53+
output[start_idx..start_idx + to_copy].copy_from_slice(&self.buffer[..to_copy]);
54+
self.buffer.drain(..to_copy);
55+
remaining = remaining.saturating_sub(to_copy);
56+
}
57+
}
58+
}
59+
60+
#[cfg(feature = "std")]
61+
impl<const OUTPUT: usize, R: Rng<OUTPUT>> std::io::Read for BufferedRng<OUTPUT, R> {
62+
fn read(&mut self, output: &mut [u8]) -> std::io::Result<usize> {
63+
self.fill(output);
64+
Ok(output.len())
65+
}
66+
67+
fn read_to_end(&mut self, buf: &mut Vec<u8>) -> std::io::Result<usize> {
68+
buf.extend_from_slice(&self.buffer);
69+
Ok(self.buffer.drain(..).count())
70+
}
71+
72+
fn read_to_string(&mut self, _buf: &mut String) -> std::io::Result<usize> {
73+
panic!("attempted to read an rng into a string")
74+
}
75+
}
76+
77+
impl<const OUTPUT: usize, R: Rng<OUTPUT>> Deref for BufferedRng<OUTPUT, R> {
78+
type Target = R;
79+
80+
fn deref(&self) -> &Self::Target {
81+
&self.rng
82+
}
83+
}
84+
85+
impl<const OUTPUT: usize, R: Rng<OUTPUT>> DerefMut for BufferedRng<OUTPUT, R> {
86+
fn deref_mut(&mut self) -> &mut Self::Target {
87+
&mut self.rng
88+
}
89+
}
90+
91+
impl<const OUTPUT: usize, R: Rng<OUTPUT> + Default> Default for BufferedRng<OUTPUT, R> {
92+
fn default() -> Self {
93+
Self::new(R::default())
94+
}
95+
}

src/lib.rs

+22-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,17 @@
3333
//! println!("Random number between 1 and 100: {}", rng.generate_range(1_u64..=100));
3434
//! println!("Random number between -100 and 50: {}", rng.generate_range(-100_i64..=50));
3535
//! ```
36+
//! ### Buffering random bytes
37+
//! ```rust
38+
//! use nanorand::{Rng, BufferedRng, WyRand};
39+
//!
40+
//! let mut thingy = [0u8; 5];
41+
//! let mut rng = BufferedRng::new(WyRand::new());
42+
//! rng.fill(&mut thingy);
43+
//! // As WyRand generates 8 bytes of output, and our target is only 5 bytes,
44+
//! // 3 bytes will remain in the buffer.
45+
//! assert_eq!(rng.buffered(), 3);
46+
//! ```
3647
//! ## Shuffling a Vec
3748
//! ```rust
3849
//! use nanorand::{Rng, WyRand};
@@ -72,8 +83,9 @@
7283
//!
7384
//! ## Feature Flags
7485
//!
75-
//! * `std` (default) - Enables Rust `std` lib features, such as seeding from OS entropy sources.
76-
//! * `tls` (default) - Enables a thread-local [`WyRand`](rand/wyrand/struct.WyRand.html) RNG (see below). Requires `tls` to be enabled.
86+
//! * `alloc` (default) - Enables Rust `alloc` lib features, such as a buffering Rng wrapper.
87+
//! * `std` (default) - Enables Rust `std` lib features, such as seeding from OS entropy sources. Requires `alloc` to be enabled.
88+
//! * `tls` (default) - Enables a thread-local [`WyRand`](rand/wyrand/struct.WyRand.html) RNG (see below). Requires `std` to be enabled.
7789
//! * `wyrand` (default) - Enable the [`WyRand`](rand/wyrand/struct.WyRand.html) RNG.
7890
//! * `pcg64` (default) - Enable the [`Pcg64`](rand/pcg64/struct.Pcg64.html) RNG.
7991
//! * `chacha` - Enable the [`ChaCha`](rand/chacha/struct.ChaCha.html) RNG. Requires Rust 1.47 or later.
@@ -82,11 +94,19 @@
8294
//! * `getrandom` - Use the [`getrandom`](https://crates.io/crates/getrandom) crate as an entropy source.
8395
//! Works on most systems, optional due to the fact that it brings in more dependencies.
8496
97+
#[cfg(feature = "alloc")]
98+
extern crate alloc;
99+
100+
#[cfg(feature = "alloc")]
101+
pub use buffer::BufferedRng;
85102
pub use gen::*;
86103
pub use rand::*;
87104
#[cfg(feature = "tls")]
88105
pub use tls::tls_rng;
89106

107+
#[cfg(feature = "alloc")]
108+
/// Provides a buffered wrapper for RNGs, preventing bits from being wasted.
109+
pub mod buffer;
90110
/// Implementation of cryptography, for CSPRNGs.
91111
pub mod crypto;
92112
/// Sources for obtaining entropy.

0 commit comments

Comments
 (0)