Expected type parameter, found struct (single type)

I am aware that there are many similar questions, but it seems to me that the error in my case is the opposite of what I read in the other questions.

I wrote a function that creates a random generator from a seed (if given), another generator (if given) or the thread generator:

use rand::{RngCore, SeedableRng, thread_rng};
use rand_pcg::Pcg64Mcg;

pub fn get_rng<R: RngCore>(parent: Option<R>, seed: Option<u64>) -> Pcg64Mcg {
    match seed {
        Some(s) => Pcg64Mcg::seed_from_u64(s),
        None => {
            let parent = parent.unwrap_or_else(|| thread_rng() as dyn RngCore); // error here
            Pcg64Mcg::from_rng(parent).expect("failed to seed random generator")
        }
    }
}

The code does not compile because the compiler expects the variable parent to be of type R, but it finds the concrete type ThreadRng:

error[E0308]: mismatched types
  --> src/utils.rs:13:51
   |
9  | pub fn get_rng<R: RngCore>(parent: Option<R>, seed: Option<u64>) -> Pcg64Mcg {
   |                - this type parameter
...
13 |             let parent = parent.unwrap_or_else(|| thread_rng());
   |                                                   ^^^^^^^^^^^^ expected type parameter `R`, found struct `rand::prelude::ThreadRng`
   |
   = note: expected type parameter `R`
                      found struct `rand::prelude::ThreadRng`

Ok, that makes sense, so let's inform the compiler that it should interpret ThreadRng as RngCore:

  • thread_rng() as dyn RngCore error: cast to unsized type
  • thread_rng() as dyn RngCore + Sized error: only auto traits can be used as additional traits in a trait object
  • thread_rng() as R error: non-primitive cast

The point is that the compiler wants parent to be of type R, i.e. something that implements RngCore; well, ThreadRng does implement that, so why doesn't it work? The similar error that I have seen are the opposite: they usually involve function arguments and the compiler gives an error because the generic type parameter covers more cases that the programmer thought about and it cannot guarantee that all of them will work. Here it's a single case and I know it works, since ThreadRng implements RngCore.

As a matter of fact, directly passing thread_rng() as the parent argument works fine, so I really think I'm missing something obvious.

The type parameter of a generic function, R in this case, is an input type, which the caller chooses. Consequently, you can't force it to be a specific type in the function body (because what if the caller chooses something else?).

1 Like

I see, that's certainly the cause of the error.

I reasoned that the caller can choose what they want, but in the problematic line, ThreadRng is only used if parent is None, i.e. R is irrelevant. Is this a case of type inference not being advanced enough?

Even if this were allowed, you'd have trouble calling it:

let rng = get_rng(None, Some(4));

The compiler won't allow this because it needs to be able to infer exactly one type for the parameter R. In other words, even though they do the same thing, these...

let rng = get_rng::<Foo>(None, Some(4));
let rng = get_rng::<Bar>(None, Some(4));

are still distinct function calls. So you end up putting a bogus type parameter at the call site, which rather defeats the point of taking an Option to begin with.

So you can either bite the bullet and make the caller pass thread_rng() explicitly, or you could just make it accept Option<&mut dyn RngCore>, so there's no type parameter to confuse inference:

pub fn get_rng(parent: Option<&mut dyn RngCore>, seed: Option<u64>) -> Pcg64Mcg
3 Likes

No, I wouldn't say so. Type inference doesn't (and can't) know much about runtime values, so it can't tell if the type parameter is actually irrelevant, as is the case here.

Depending on the use case, it might make sense to split this into two functions

pub fn get_rng(seed: Option<u64>) -> Pcg64Mcg;

pub fn get_child_rng<R: RngCore>(parent: R, seed: Option<u64>) -> Pcg64Mcg;

(This is one Rust idiom for cases where function overloading would be used in, say, C++ or Java.)

To avoid code duplication, get_rng would call get_child_rng and pass it a ThreadRng.

3 Likes