Skip to content

Commit 175b9d2

Browse files
committed
Use UnsafeCell in ThreadRng
1 parent 67bb510 commit 175b9d2

File tree

1 file changed

+26
-8
lines changed

1 file changed

+26
-8
lines changed

src/thread_rng.rs

+26-8
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,32 @@
1010

1111
//! Thread-local random number generator
1212
13-
use std::cell::RefCell;
13+
use std::cell::UnsafeCell;
1414
use std::rc::Rc;
1515

1616
use {RngCore, CryptoRng, SeedableRng, EntropyRng};
1717
use prng::hc128::Hc128Core;
1818
use {Distribution, Uniform, Rng, Error};
1919
use reseeding::ReseedingRng;
2020

21+
// Rationale for using `UnsafeCell` in `ThreadRng`:
22+
//
23+
// Previously we used a `RefCell`, with an overhead of ~15%. There will only
24+
// ever be one mutable reference to the interior of the `UnsafeCell`, because
25+
// we only have such a reference inside `next_u32`, `next_u64`, etc. Within a
26+
// single thread (which is the definition of `ThreadRng`), there will only ever
27+
// be one of these methods active at a time.
28+
//
29+
// A possible scenario where there could be multiple mutable references is if
30+
// `ThreadRng` is used inside `next_u32` and co. But the implementation is
31+
// completely under our control. We just have to ensure none of them use
32+
// `ThreadRng` internally, which is nonsensical anyway. We should also never run
33+
// `ThreadRng` in destructors of its implementation, which is also nonsensical.
34+
//
35+
// The additional `Rc` is not strictly neccesary, and could be removed. For now
36+
// it ensures `ThreadRng` stays `!Send` and `!Sync`, and implements `Clone`.
37+
38+
2139
// Number of generated bytes after which to reseed `TreadRng`.
2240
//
2341
// The time it takes to reseed HC-128 is roughly equivalent to generating 7 KiB.
@@ -32,18 +50,18 @@ const THREAD_RNG_RESEED_THRESHOLD: u64 = 32*1024*1024; // 32 MiB
3250
/// [`thread_rng`]: fn.thread_rng.html
3351
#[derive(Clone, Debug)]
3452
pub struct ThreadRng {
35-
rng: Rc<RefCell<ReseedingRng<Hc128Core, EntropyRng>>>,
53+
rng: Rc<UnsafeCell<ReseedingRng<Hc128Core, EntropyRng>>>,
3654
}
3755

3856
thread_local!(
39-
static THREAD_RNG_KEY: Rc<RefCell<ReseedingRng<Hc128Core, EntropyRng>>> = {
57+
static THREAD_RNG_KEY: Rc<UnsafeCell<ReseedingRng<Hc128Core, EntropyRng>>> = {
4058
let mut entropy_source = EntropyRng::new();
4159
let r = Hc128Core::from_rng(&mut entropy_source).unwrap_or_else(|err|
4260
panic!("could not initialize thread_rng: {}", err));
4361
let rng = ReseedingRng::new(r,
4462
THREAD_RNG_RESEED_THRESHOLD,
4563
entropy_source);
46-
Rc::new(RefCell::new(rng))
64+
Rc::new(UnsafeCell::new(rng))
4765
}
4866
);
4967

@@ -80,20 +98,20 @@ pub fn thread_rng() -> ThreadRng {
8098
impl RngCore for ThreadRng {
8199
#[inline(always)]
82100
fn next_u32(&mut self) -> u32 {
83-
self.rng.borrow_mut().next_u32()
101+
unsafe { (*self.rng.get()).next_u32() }
84102
}
85103

86104
#[inline(always)]
87105
fn next_u64(&mut self) -> u64 {
88-
self.rng.borrow_mut().next_u64()
106+
unsafe { (*self.rng.get()).next_u64() }
89107
}
90108

91109
fn fill_bytes(&mut self, bytes: &mut [u8]) {
92-
self.rng.borrow_mut().fill_bytes(bytes)
110+
unsafe { (*self.rng.get()).fill_bytes(bytes) }
93111
}
94112

95113
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
96-
self.rng.borrow_mut().try_fill_bytes(dest)
114+
unsafe { (*self.rng.get()).try_fill_bytes(dest) }
97115
}
98116
}
99117

0 commit comments

Comments
 (0)