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));
}
}
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.
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.
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.