Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can impl trait not be used to return multiple / conditional types?

I'm trying to get a random number generator. Since OsRng::new() can fail, I'd like to fall back to thread_rng() if I have to:

extern crate rand; // 0.5.5

use rand::{thread_rng, OsRng, RngCore};

fn rng() -> impl RngCore
{
    match OsRng::new() {
        Ok(rng) => rng,
        Err(e) => thread_rng()
    }
}

However, I get this error message which I cannot understand:

error[E0308]: match arms have incompatible types
 --> src/lib.rs:6:5
  |
6 | /     match OsRng::new() {
7 | |         Ok(rng) => rng,
8 | |         Err(e) => thread_rng(),
  | |                   ------------ match arm with an incompatible type
9 | |     }
  | |_____^ expected struct `rand::OsRng`, found struct `rand::ThreadRng`
  |
  = note: expected type `rand::OsRng`
             found type `rand::ThreadRng`

Why is the compiler expecting rand::OsRng here instead of an implementation of RngCore? If I remove the match and directly return thread_rng(), I don't get above error message.

I do not believe that this is a duplicate of How do I return an instance of a trait from a method?, as the other question is asking about how one can return a trait from a function, and this question is about why the compiler will not allow me to return a trait but wants me to return an OsRng which is not the return type of the function.

like image 689
msrd0 Avatar asked Aug 24 '18 09:08

msrd0


2 Answers

impl Trait is not equivalent to returning an interface or base class object. It's a way of saying "I don't want to write the name of the specific type I'm returning". You're still returning a value of a single, specific type; you just aren't saying which type.

Each of those branches is returning different types, hence the problem. Implementing the same trait is not enough.

What you likely want in this specific case is a trait object like Box<dyn RngCore>.

extern crate rand; // 0.6.5

use rand::{rngs::OsRng, thread_rng, RngCore};

fn rng() -> Box<dyn RngCore> {
    match OsRng::new() {
        Ok(rng) => Box::new(rng),
        Err(_) => Box::new(thread_rng()),
    }
}

Note: if you are using a slightly older version of Rust, you may need to remove the dyn keyword. It's optional in the previous (2015) edition of Rust.

like image 119
DK. Avatar answered Nov 03 '22 09:11

DK.


DK. has already explained why, but I'd like to provide an alternative workaround.

As mentioned in Conditionally iterate over one of several possible iterators, you can create an enum that implements a trait if both of its component types do. For example:

extern crate rand; // 0.6.5

use rand::{rngs::OsRng, thread_rng, RngCore};

fn rng() -> impl RngCore {
    match OsRng::new() {
        Ok(rng) => EitherRng::Left(rng),
        Err(_) => EitherRng::Right(thread_rng()),
    }
}

enum EitherRng<L, R> {
    Left(L),
    Right(R),
}

impl<L, R> RngCore for EitherRng<L, R>
where
    L: RngCore,
    R: RngCore,
{
    fn next_u32(&mut self) -> u32 {
        match self {
            EitherRng::Left(l) => l.next_u32(),
            EitherRng::Right(r) => r.next_u32(),
        }
    }

    fn next_u64(&mut self) -> u64 {
        match self {
            EitherRng::Left(l) => l.next_u64(),
            EitherRng::Right(r) => r.next_u64(),
        }
    }

    fn fill_bytes(&mut self, b: &mut [u8]) {
        match self {
            EitherRng::Left(l) => l.fill_bytes(b),
            EitherRng::Right(r) => r.fill_bytes(b),
        }
    }

    fn try_fill_bytes(&mut self, b: &mut [u8]) -> Result<(), rand::Error> {
        match self {
            EitherRng::Left(l) => l.try_fill_bytes(b),
            EitherRng::Right(r) => r.try_fill_bytes(b),
        }
    }
}

The either crate provides a lot of these types of implementations for fundamental traits.

See also:

  • How do I conditionally return different types of futures?
like image 25
Shepmaster Avatar answered Nov 03 '22 10:11

Shepmaster