Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics plus dynamic dispatch

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`
like image 916
mrspl Avatar asked Mar 06 '23 04:03

mrspl


1 Answers

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);
like image 130
Sven Marnach Avatar answered Mar 17 '23 03:03

Sven Marnach