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.
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.
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With