Why does `?` require `From` instead of `Into`?


#1

I learned that the ? operator requires the function’s result error type (let’s call it F) to implement From<E>, where E is the error type of the expression I’m applying the operator.

I also recently learned that if I implement From<E> for F, I also get a “free” implementation of Into<F> for E. However, the reciprocal is not true; if I implement Into<E> for F I don’t get an implementation of From<F> for E.

My question is, why doesn’t the ? operator require the expression’s result error type to implement Into<F> instead of requiring F to implement From<E>? Wouldn’t it be more flexible?

Thanks in advance,

Janito


#2

From the Into docs:

Library authors should not directly implement this trait, but should prefer implementing the From trait, which offers greater flexibility and provides an equivalent Into implementation for free, thanks to a blanket implementation in the standard library.

So discouraging people from incorrectly implementing Into is not necessarily a bad thing.


#3

I see, so the idea is to force the programmer to write more code if there’s a desire or need to use Into. I understand that now, thanks!

I do think it’s a high price to pay in some occasions, though (e.g.: the need to return an error type of another crate in your function, so you can’t code a new From implementation), but that’s just my 2 cents :slight_smile:

Thanks again,

Janito


#4

There’s also this comment in the original RFC which is a response to the same question right above it.


#5

That’s a pretty complicated example, but it helped me see that there’s an advantage to use From in some cases. That convinced me that From is a superior approach :slight_smile:

Thanks!


#6

Can you give an example where Into is possible to implement but From is not?

Implementing From<MyType> for TheirType is allowed.


#7

What I actually meant was that there are situations where I would like to return their error type, after converting from my error type, and the ? operator would reduce the amount of code necessary.


#8

So like this? What’s wrong with From?

use std::io;

struct MyError;

impl From<MyError> for io::Error {
    fn from(_: MyError) -> io::Error {
        io::Error::new(io::ErrorKind::Other, "my error")
    }
}

fn my_f() -> Result<(), MyError> {
    Ok(())
}

pub fn pub_f() -> Result<(), io::Error> {
    my_f()?;
    Ok(())
}

fn main() {
    pub_f().unwrap();
}

#9

I think I’m confused. I didn’t think your example would work. Wasn’t there a rule that I can’t implement a trait for a type that’s not from my crate? I thought you couldn’t implement From for external types. Did I understand things incorrectly?

Thank you very much for your reply, I didn’t even know I was missing something.


#10

For non-generic traits, that’s true - you cannot implement a foreign non-generic trait for a foreign type. With generic traits, you can use local types in the remote trait and still stay within the orphan rules. The following has more details: