Consider the case where I have a function make_numbers
which should create a string of random numbers, but where I want to decide at runtime (user input) what kind of random number generator should be used. To make it even more difficult, let's assume the make_numbers
function to be generic over the type of numbers to be generated.
I wrote what I want to achieve with pseudo code, and I understand why this doesn't work. However, I don't know what an idiomatic way in Rust could look like to achieve this?
My naive ideas would be:
Box<Rng>
, but that doesn't work since Rng
has generic functions.StdRng
and XorShiftRng
, but I cannot really think of a nice way to write this.Can you give me some hints as to what a nice solution of this particular problem would look like?
Note: This question is not so much about the different match arms having different types (solutions could be Box
or enum, as indicated above) - but how to apply these solutions in this case.
extern crate rand;
use rand::{Rng, SeedableRng, StdRng};
use rand::prng::XorShiftRng;
use std::string::String;
use rand::distributions::{Distribution, Standard};
use std::fmt::Display;
// Generic function that should work with any type of random number generator
fn make_numbers<T, R: Rng>(rng: &mut R) -> String
where T: Display, Standard: Distribution<T>
{
let mut s = String::new();
for _i in 0..10 {
s.push_str(format!("_{}", rng.gen::<T>()).as_str());
}
s
}
fn main() {
let use_std = true; // -> assume that this will be determined at runtime (e.g. user input)
// Pseudo code, will not work.
let mut rng = match use_std {
true => StdRng::from_seed(b"thisisadummyseedthisisadummyseed".to_owned()),
false => XorShiftRng::from_seed(b"thisisadummyseed".to_owned())
};
let s = make_numbers::<u8>(&mut rng);
// ... do some complex stuff with s ...
print!("{}", s)
}
error[E0308]: match arms have incompatible types
--> src/main.rs:24:19
|
24 | let mut rng = match use_std {
| ___________________^
25 | | true => StdRng::from_seed(b"thisisadummyseedthisisadummyseed".to_owned()),
26 | | false => XorShiftRng::from_seed(b"thisisadummyseed".to_owned())
| | ------------------------------------------------------ match arm with an incompatible type
27 | | };
| |_____^ expected struct `rand::StdRng`, found struct `rand::XorShiftRng`
|
= note: expected type `rand::StdRng`
found type `rand::XorShiftRng`
You noticed yourself that you can't use Box<dyn Rng>
since the Rng
trait is not object-safe. The rand
crate offers a solution for this, though: The foundation of each RNG is provided by the trait RngCore
, which is object-safe, and Box<dyn RngCore>
also implements Rng
by means of these two trait implementations:
impl<R: RngCore + ?Sized> RngCore for Box<R>
impl<R: RngCore + ?Sized> Rng for R
The first implementation makes sure that Box<dyn RngCore>
is RngCore
itself, while the second one implements Rng
for all RngCore
objects. In effect, you will be able to call all Rng
methods on RngCore
trait objects, and the implementation dynamically dispatches to the required RngCore
methods under the hood.
Exploiting this, you can use the following code:
let mut rng: Box<dyn RngCore> = if use_std {
Box::new(
StdRng::from_seed(b"thisisadummyseedthisisadummyseed".to_owned())
)
} else {
Box::new(
XorShiftRng::from_seed(b"thisisadummyseed".to_owned())
)
};
let s = make_numbers::<u8, _>(&mut rng);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With