Earth Engine
Earth Engine

Reputation: 10476

Why is mapping a Result from parsing a string using Into::into unable to infer a type?

The following (playground link)

#[derive(Debug)]
struct MyError();

impl From<::std::net::AddrParseError> for MyError {
    fn from(_e: ::std::net::AddrParseError) -> MyError {
        MyError()
    }
}

fn accept_addr(_addr: ::std::net::SocketAddr) {}

fn main() -> Result<(), MyError> {
    let addr = "127.0.0.1:23456".parse();
    let addr = addr.map_err(|e| e.into())?;
    Ok(accept_addr(addr))
}

does not work. Error:

error[E0282]: type annotations needed
  --> src/main.rs:14:30
   |
14 |     let addr = addr.map_err(|e| e.into())?;
   |                              ^ consider giving this closure parameter a type

I cannot resolve this issue by following the advice from the error message above. If I change the code to:

let addr = addr.map_err(|e: ::std::net::AddrParseError| e.into())?;

I get another error:

error[E0282]: type annotations needed
  --> src/main.rs:14:16
   |
14 |     let addr = addr.map_err(|e: ::std::net::AddrParseError| e.into())?;
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for `_`

My solution is to use From instead:

let addr = addr.map_err(|e| <MyError as From<_>>::from(e))?; // Worked!!

Even better, I figured out later that I don't even need to map the error:

let addr = "127.0.0.1:23456".parse()?;
Ok(accept_addr(addr))

I know that type inference is never easy, but why doesn't the above code infer the type properly?

Upvotes: 2

Views: 995

Answers (2)

Calculator
Calculator

Reputation: 2799

Concerning your second error: Reducing your example a bit by replacing the result obtained from parsing with another custom error (MyError2) reproduces the same issue (|e: MyError2| e.into() again does not help):

#[derive(Debug)]
struct MyError();
struct MyError2();

impl From<MyError2> for MyError {
    fn from(_e: MyError2) -> MyError{
        MyError()
    }
}

fn main() -> Result<(),MyError> {
    let addr = Err(MyError2{});
    addr.map_err(|e| e.into())?;
    Ok(())
}
error[E0282]: type annotations needed
  --> src/main.rs:20:5
   |
20 |     addr.map_err(|e| e.into())?;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for `_`

error: aborting due to previous error

Replacing ? with its macro definition (try!) from the standard library reveals:

macro_rules! try {
    ($e:expr) => (match $e {
        Ok(val) => val,
        Err(err) => return Err(::std::convert::From::from(err)),
    });
}

fn main() -> Result<(),MyError> {
    let addr = Err(MyError2{});
    let addr = try!(addr.map_err(|e| e.into()));
    Ok(addr)
}
error[E0282]: type annotations needed
  --> src/main.rs:14:13
   |
14 |         Err(err) => return Err(::std::convert::From::from(err)),
   |             ^^^ cannot infer type for `_`
...
20 |     let addr = try!(addr.map_err(|e| e.into()));
   |                -------------------------------- in this macro invocation

The From::from() applied on the err in the macro definition is responsible for the inference fail. Replacing this line: Err(err) => return Err(::std::convert::From::from(err)), with Err(err) => return Err(err), resolves the issue - the program compiles.

The reason is that by placing two conversions via From::from() between MyError and MyError2 this conversion pipeline becomes ambiguous. The compiler can't determine the intermediate type.

Example - two valid options (note that From::from is implemented reflexively):

  • MyError2 -> MyError2 -> MyError

    compiles: let addr = addr.map_err(|e| {let e2: MyError2 = e.into(); e2})?;

  • MyError2 -> MyError -> MyError

    compiles: let addr = addr.map_err(|e| {let e2: MyError = e.into(); e2})?;

Upvotes: 3

Shepmaster
Shepmaster

Reputation: 432089

Parsing a string can return any number of types:

pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err>
where
    F: FromStr, 

Your code doesn't have any hints about what should be parsed out of the string before you start messing with it, so the compiler has no idea which implementation of parse to pick:

let addr = "127.0.0.1:23456".parse();
let addr = addr.map_err(|e| e.into())?;

You are then trying to convert an unknown type into a MyError, thus the error.

let addr = addr.map_err(|e: ::std::net::AddrParseError| e.into())?;

Knowing the error type isn't enough because multiple types can implement FromStr with the same error type:

pub trait FromStr {
    type Err;
    fn from_str(s: &str) -> Result<Self, Self::Err>;
}

This means that the compiler still doesn't know what to parse as.

In general, the compiler will not make multiple leaps of inference because the search space can become exponentially huge. In other cases, the current ad hoc trait system doesn't know enough / try hard enough to make certain types of inference work. It's possible that switching to chalk will make the type system a bit more mathematically rigorous.


let addr = addr.map_err(|e| <MyError as From<_>>::from(e))?;

This can be written more simply:

let addr = addr.map_err(|e| MyError::from(e))?;

I don't even need to map the error

Yes, because ? does it for you.

Upvotes: 2

Related Questions