Lazily initialized static variable with random number generator


#1

Hi lovely helpful people! :slight_smile:

I have made a simple crate for generating random “lorem ipsum” text: lipsum. It uses a Markov chain internally to generate the text. Currently, the code use the thread-local random number generator directly when it needs some randomness. It looks similar to this:

let mut rng = rand::thread_rng();
let next = rng.choose(next_words).unwrap();

I would like to store the RNG in the MarkovChain struct instead. That way you can seed the RNG and get consistent output when needed.

However, I am also initializing a static MarkovChain value with lazy_static! — this is to save the overhead of training the chain every time a bit of lorem ipsum text is needed.

Expanding the MarkovChain struct to

pub struct MarkovChain<'a> {
    pub map: HashMap<Bigram<'a>, Vec<&'a str>>,
    pub rng: rand::ThreadRng, // <-- the new struct field
}

gives me this lovely error message when compiling the code:

error[E0277]: the trait bound `std::rc::Rc<std::cell::RefCell<rand::reseeding::ReseedingRng<rand::StdRng, rand::ThreadRngReseeder>>>: std::marker::Sync` is not satisfied in `MarkovChain<'static>`
   --> src/lib.rs:213:1
    |
213 | / lazy_static! {
214 | |     /// Markov chain generating lorem ipsum text.
215 | |     static ref LOREM_IPSUM_CHAIN: MarkovChain<'static> = {
216 | |         let mut chain = MarkovChain::new();
...   |
220 | |     };
221 | | }
    | |_^ `std::rc::Rc<std::cell::RefCell<rand::reseeding::ReseedingRng<rand::StdRng, rand::ThreadRngReseeder>>>` cannot be shared between threads safely
    |
    = help: within `MarkovChain<'static>`, the trait `std::marker::Sync` is not implemented for `std::rc::Rc<std::cell::RefCell<rand::reseeding::ReseedingRng<rand::StdRng, rand::ThreadRngReseeder>>>`
    = note: required because it appears within the type `rand::ThreadRng`
    = note: required because it appears within the type `MarkovChain<'static>`
    = note: required by `lazy_static::lazy::Lazy`
    = note: this error originates in a macro outside of the current crate

The code in question. I believe I roughly understand what the error says: the ThreadRng RNG is not thread safe (not Sync) and this is required by the voodoo done by the lazy_static! macro.

Does anybody have a good tip for this situation?

I don’t intend to share the MarkovChain values between threads — I would just like them to carry a bit of state. It doesn’t sound like a hard problem :slight_smile:


#2

Can you use thread_local!?


#3

Wow, yes, I can indeed! With the help of a RefCell I managed to get the behavior I was looking for. Thanks a lot! :slight_smile:

Now I wonder a bit what the advantage of lazy_static! is… perhaps that it gives you a variable that you can treat more or less like the original type due to the Deref trait?


#4

That’s one (I actually think Deref is much nicer, see https://github.com/rust-lang-nursery/lazy-static.rs/issues/82). The other advantage is, of course, that it results in a global static.