4

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:

  1. Use Box<Rng>, but that doesn't work since Rng has generic functions.
  2. Use an enum over 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` 
3
  • Other recommendations: an if..else statement would be more readable than a match statement on a bool. The call to make_numbers also has to list both type parameters, even if just filled with _ to let the compiler infer them: make_numbers::<u8, _>(&mut rng). The implementation of make_numbers can become a one-liner with the iterator API: (0..10).map(|_| format!("_{}", rng.gen::<T>())).collect(). Finally, don't forget to call rustfmt on your code. Commented Oct 28, 2018 at 11:21
  • @E_net4 I agree this is a duplicate, but there is a specific twist here, since the Rng trait isn't object safe, and the RngCore trait needs to be used instead. Commented Oct 28, 2018 at 11:50
  • Thanks for the comments. @E_net4, these are good suggestions! Regarding the duplicate, I agree that this might look like a duplicate, but as Sven mentioned, the boxing solution doesn't work in this case and my troubles are more about how to find a good solution when the underlying trait is not box-able (object-safe)... Commented Oct 28, 2018 at 12:53

2 Answers 2

6

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); 
Sign up to request clarification or add additional context in comments.

2 Comments

This is a nice solution to this precise problem, thanks. I hadn't really thought about using RngCore instead. In situations where such an object-safe trait is not provided, could I always implement a "dummy" trait and an implementation such as impl<R: RngCore + ?Sized> Rng for R? Or am I missing something?
@mrspl The reason this works in this case is that all "required" methods are object-safe, and the non-object-safe methods in the Rng trait are all implemented in terms of the object-safe methods in the RngCore trait, i.e. the Rng trait only contains "provided" methods and no "required" methods. If your trait can be split up this way, you can use the same design, but you can never dynamically dispatch to a non-object-safe method.
1

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.