Reputation: 501
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`
Upvotes: 4
Views: 421
Reputation: 601539
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:
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);
Upvotes: 6
Reputation: 35983
I think you understand that the types of your match
arms must be the same. (Otherwise, please refer to the suggested duplicate.)
One other option I see in your particular case is just calling make_numbers
for each arm:
fn main() {
let use_std = true;
let s = match use_std {
true => make_numbers::<u8, _>(&mut StdRng::from_seed(b"thisisadummyseedthisisadummyseed".to_owned())),
false => make_numbers::<u8, _>(&mut XorShiftRng::from_seed(b"thisisadummyseed".to_owned()))
};
print!("{}", s)
}
I see that this may not make sense if you have lots of additional parameters into make_numbers
.
In such cases, I resorted to macros:
fn main() {
let use_std = true;
macro_rules! call_make_numbers(($t:ty, $rng:ident, $str:expr) => {
make_numbers::<$t, _>(&mut $rng::from_seed($str.to_owned()))
});
let s = match use_std {
true => call_make_numbers!(u8, StdRng, b"thisisadummyseedthisisadummyseed"),
false => call_make_numbers!(u8, XorShiftRng, b"thisisadummyseed"),
};
print!("{}", s)
}
Upvotes: 1