Hello everyone,
I am trying to implement a safe and efficient thread local rng. The rand::thread_rng() not works for me, because I need a deterministic seedable RNG, so the results are reproducable. I am currently storing my RNG in a struct and pass as &mut through function calls. My goal is to store the RNG in a thread local static variable, so the function calls would be cleaner. (As don't have to pass references of the rng.)
I've looked at the implementation of rand::rngs::ThreadRng as well as the #968 issue, and the best solution so far seemst to be something like:
use rand_pcg::Pcg64 as MyRng;
thread_local!(
static THREAD_RNG_KEY: Rc<RefCell<MyRng>> = { ... }
);
pub struct ThreadRng {
rng: Rc<RefCell<MyRng>>,
}
pub fn thread_rng() -> ThreadRng {
ThreadRng { rng: THREAD_RNG_KEY.with(|rng| rng.clone()) }
}
impl RngCore for ThreadRng {
fn next_u32(&mut self) -> u32 {
self.rng.borrow_mut().next_u32()
}
}
For this the problem mentined in #968 is
ThreadRngdestructors must be run or memory is leaked.
Unless a thread panics, this shouldn't cause problem, or is it? (In every other cases the destructors are guaranteed to run I think.) (In my case if a thread panics the whole program is terminated anyway... so that's not really a problem for me.)
So that I can safely store a ThreadRng in local variables and even in structs. (And myabe use even in the destructors of other thread local variables, although it is unlikely.) (Calling THREAD_RNG_KEY.with() for every number would be really painful.)
Is there any other safety issues related to this solution?
Safety is important, as neither I, nor the later developers of the code (will) have advanced programming skills.
Final solution:
Use UnsafeCell instead of RefCell (In a way, that unsafe code is only used in the implementation of RngCore.) Something like:
thread_local!(
static THREAD_RNG_KEY: Rc<UnsafeCell<MyRng>> = { ... }
);
pub struct ThreadRng {
rng: Rc<UnsafeCell<MyRng>>,
}
pub fn thread_rng() -> ThreadRng {
ThreadRng { rng: THREAD_RNG_KEY.with(|rng| rng.clone()) }
}
impl RngCore for ThreadRng {
fn next_u32(&mut self) -> u32 {unsafe{(*self.rng.get()).next_u32()}}
fn next_u64(&mut self) -> u64 {unsafe{(*self.rng.get()).next_u64()}}
fn fill_bytes(&mut self, slice: &mut [u8]) {unsafe{(*self.rng.get()).fill_bytes(slice)}}
fn try_fill_bytes(&mut self, slice: &mut [u8]) -> std::result::Result<(), rand::Error> {unsafe{(*self.rng.get()).try_fill_bytes(slice)}}
}
(RefCell had about 50% overhead with Pcg64, according to my measurements.)