Search code examples
testingrustrandomdependency-injection

Dependency injection of a SeedableRng in a Rust project with `rand::rngs::ThreadRng`


I'm starting with a regular &mut ThreadRng in all my functions, but it limits dependency injection capacities. There are SeedableRng ofc, but how to use it in places where initially it was a ThreadRng ?

Coming from other languages I'd like to specify some interface in the fn declaration, but &mut dyn Rng doesn't work because Rng is not object safe.

What I ended up with is passing &mut StdRng everywhere and

let mut rng = match config.seed {
  Some(seed) => StdRng::seed_from_u64(seed),
  _ => StdRng::from_rng(rand::thread_rng()).unwrap_or(StdRng::from_entropy())
};

But it's awkward to init 1 Rng only to setup another Rng

I also searched the pattern StdRng::from_rng(rand::thread_rng()) and it's just 56 samples in github, from 141k of using rand::thread_rng()

So what's the best signature for a fn accepting Rng and the best way to init with an optional seed?

p.s. I don't care about wasm/etc runtimes, it's just a regular server-side app


Solution

  • The rand documentation has a bit about generic usage in it you can find the following:

    Since Rng: RngCore and every RngCore implements Rng, it makes no difference whether we use R: Rng or R: RngCore

    From that we can deduce, that we can just use the object safe RngCore in place of Rng in situations that require dynamic dispatch:

    use rand::{Rng, RngCore};use rand::rngs::StdRng;
    fn main() {
        let seed = None;
        let mut std_rng;
        let mut thread_rng;
        let rng: &mut dyn RngCore = match seed {
            Some(seed) => {
                std_rng = StdRng::seed_from_u64(seed);
                &mut std_rng
            }
            _ => {
                thread_rng = rand::thread_rng();
                &mut thread_rng
            }
        };
        
        random(rng);
    }
    
    fn random(rng: &mut dyn RngCore) {
        println!("{}", rng.gen_range(0..9));
    }
    

    Though the more idiomatic way would be to use static dispatch instead, and that does work with Rng:

    fn random(mut rng: impl Rng) {
        println!("{}", rng.gen_range(0..9));
    }