The implementation of rand crate's thread rng basically looks like this (please point out if I'm missing something):
use std::{thread_local, rc::Rc, cell::UnsafeCell};
pub struct State;
impl State {
pub fn gen(&mut self) -> u32 {
todo!()
}
}
thread_local! {
static THREAD_RNG: Rc<UnsafeCell<State>> = Rc::new(UnsafeCell::new(State));
}
pub struct ThreadRng(Rc<UnsafeCell<State>>);
pub fn thread_rng() -> ThreadRng {
ThreadRng(THREAD_RNG.with(Clone::clone))
}
impl ThreadRng {
pub fn access(&mut self) -> u32 {
let state = unsafe { &mut *self.0.get() };
state.gen()
}
}
It uses UnsafeCell
to bypass the restriction that LocalKey
itself and Rc
can only get immutable references, and get pointer directly and convert to mutable reference. Rc
's !Sync
and !Send
guarantee that it can only be used in the current thread, which logically seems to guarantee that there is only one place to access state at the same time. If data race can be avoided by guaranteeing that data can only be used in the current thread, then why can LocalKey
itself only get immutable references? Can I implement another thread local state in a similar way to the above code and also ensure that there is no data race?