Expression to produce StdRng or ThreadRng

I have a program that makes a random choice and might be run with a seed, e.g., for testing. How can I write an expression that produces either a StdRng or ThreadRng? For instance, this trivial case works:

use rand::prelude::SliceRandom;
use rand::{rngs::StdRng, SeedableRng};

fn main() {
    let choices = ["foo", "bar", "baz"];
    for seed in &[None, Some(1), Some(2), None] {
        if let Some(val) = seed {
            println!(
                "{:?}",
                choices.choose(&mut StdRng::seed_from_u64(*val))
            );
        } else {
            println!("{:?}", choices.choose(&mut rand::thread_rng()));
        };
    }
}

But this does not:

use rand::prelude::SliceRandom;
use rand::{rngs::StdRng, SeedableRng};

fn main() {
    let choices = ["foo", "bar", "baz"];
    for seed in &[None, Some(1), Some(2), None] {
        let mut rng = if let Some(val) = seed {
            StdRng::seed_from_u64(*val)
        } else {
            rand::thread_rng()
        };
        println!("{:?}", choices.choose(&mut rng));
    }
}

Because "if and else have incompatible types".

Put Box::new() around both branches of the if, individually, and declare the type of rng to be Box<dyn Rng>. That enables dynamic dispatch between the two types when rng is used.

2 Likes

Hmm, that's a cool idea. I don't think it works, but maybe I did it wrong:

use rand::prelude::SliceRandom;
use rand::{rngs::StdRng, Rng, SeedableRng};

fn main() {
    let choices = ["foo", "bar", "baz"];
    for seed in &[None, Some(1), Some(2), None] {
        let mut rng: Box<dyn Rng> = if let Some(val) = seed {
            Box::new(StdRng::seed_from_u64(*val))
        } else {
            Box::new(rand::thread_rng())
        };
        println!("{:?}", choices.choose(&mut rng));
    }
}

I get the error "the trait Rng cannot be made into an object"

Can you just create a enum, or would this be to inconvenient (you would need to write some boilerplate code to pass the method calls of Rng through)?

An enum is not out of the question, and I'll only be saving a few characters in the source code. My current method is not too onerous, but I was wondering if there was some commonality between the two values that would allow for one representation. I really thought the dyn Rng should work.

1 Like

You can use dyn RngCore instead. This still implements Rng so you can call all the same methods on it.

The reason dyn Rng doesn't work is that Rng is not an object-safe trait, because it has methods with type generic parameters and the generated vtable for dyn Rng would have to contain (in principle) an unbounded number of function pointers, one for each monomorphization of a generic method. The way rand gets around this is to define an object-safe trait RngCore and then write a blanket impl<T: RngCore + ?Sized> Rng for T, so that dyn RngCore implements Rng and can be used in much the same way as a hypothetical dyn Rng.

1 Like

Thanks! That does work, and your explanation was really helpful.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.