Rust ? operator does not work with Into<T> trait

Viewed 54

I was writting a function to flat a Result. This is what I got.

type BoxDyn = Box<dyn Error + Send + Sync>;
fn flat_result<U, T, G>(r: Result<Result<U, G>, T>) -> Result<U, BoxDyn>
where
    T: Into<BoxDyn>,
    G: Into<BoxDyn>,
{
    match r {
        Err(x) => Err(x.into()),
        Ok(x) => match x {
            Err(x) => Err(x.into()),
            Ok(x) => Ok(x),
        },
    }
}

It works fine. But I should be able to write it using two ? like this.

type BoxDyn = Box<dyn Error + Send + Sync>;
fn flat_result<U, T, G>(r: Result<Result<U, G>, T>) -> Result<U, BoxDyn>
where
    T: Into<BoxDyn>,
    G: Into<BoxDyn>,
{
    Ok(r??)
}

When I try this, I get this error:

`G` cannot be shared between threads safely
required because of the requirements on the impl of `From<G>` for `Box<dyn std::error::Error + Send + Sync>`
required because of the requirements on the impl of `FromResidual<Result<Infallible, G>>` for `Result<U, Box<dyn std::error::Error + Send + Sync>>`

But it seems to me that it should not happen because the documentation says that the ? is equivalent to a match expression, where the Err(err) branch expands to an early return Err(From::from(err)). And also From<T> for U implies Into<U> for T. So it would not matter What G or T are, if they implement Into it should work. What is the problem?

1 Answers

You are right that From implies Into but not that into implies From. And remember that ? uses From, not Into.

As it happens there is this impl in the standard library that gets in the way:

impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a>

Since your BoxDyn alias is exactly that with 'a = 'static, you have an implementation for From<E>. But for that to work your error types T and G has to satisfy Error + Send + Sync + 'a. But your only constraints are that they are Into<Error> that is actually unrelated to what is needed. Remember that From implies Into but Into does not imply From.

It works if you write instead:

fn flat_result<U, T, G>(r: Result<Result<U, G>, T>) -> Result<U, BoxDyn>
where
    T: Error + Send + Sync + 'static,
    G: Error + Send + Sync + 'static,
{
    Ok(r??)
}

But I think that the proper way to write that would be to require the From trait:

fn flat_result<U, T, G>(r: Result<Result<U, G>, T>) -> Result<U, BoxDyn>
where
    BoxDyn: From<T> + From<G>,
{
    Ok(r??)
}

Why does it work with the manual implementation? Well, because you are using Into instead of From of course. Change it to BoxDyn::from(x) and it will fail just the same.

Related