Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I get the error "trait bound FromStr is not satisfied" because of a missing type annotation?

I encountered a compile error that seems to be highlighting something I don't understand about the type system.

I want to convert a string to an integer, with a custom panic message if the string isn't a valid integer. I do a match on the Result that's returned by parse():

fn main() {
    let x = match "23".parse() {
        Ok(int) => int,
        Err(_) => panic!("Not an integer!"),
    };
    println!("x plus 1 is {}", x+1);
}

(If this were really all I was doing in my program, I would just use expect(), but there's more to it in the real program.)

I would expect the output 24 when compiled and run. Instead, the following compiler error appears:

error[E0277]: the trait bound `(): std::str::FromStr` is not satisfied
 --> main.rs:2:24
  |
2 |     let x = match "23".parse() {
  |                        ^^^^^ the trait `std::str::FromStr` is not implemented for `()`

The issue seems to be that Rust doesn't know what type I'm trying to parse to, and it makes sense this could be a problem. If I change line 2 to the following, the error goes away:

    let x: i32 = match "23".parse() {

Why did I get this error message, instead of one indicating that a type annotation was required? The message appears to be complaining that the error arm does not return anything (or more precisely, that what it returns -- namely nothing -- doesn't implement the FromStr trait), but it does not make any sense to me that, after calling panic!, the type of the output of that arm of the match could have any effect whatsoever -- the program is presumably going to unwind the stack and terminate immediately at that point, so type safety would seem irrelevant!

One hint is that if instead of calling panic!, I simply return an integer (e.g., Err(_) => 0), the code compiles fine (and works as expected). It seems that in this case, Rust correctly infers the type to be i32 the first time around and doesn't run down whatever code path is leading to the confusing error.

like image 432
Soren Bjornstad Avatar asked Jun 28 '20 22:06

Soren Bjornstad


Video Answer


1 Answers

The message appears to be complaining that the error arm does not return anything (or more precisely, that what it returns -- namely nothing -- doesn't implement the FromStr trait).

Actually you were right the first time. The error arm never returns. The return type of panic! is literally called the never type (!) and it's different from the unit type (()) which does return, although what it returns is "nothing".

Pro-tip: functions which never return are called divergent functions.

it doesn't make any sense to me that, after calling panic!, the type of the output of that arm of the match could have any effect whatsoever.

It doesn't. The never type has no influence on type inference and can be used in place of any other type. For example, this program compiles without any errors or warnings:

#![allow(unreachable_code)]

fn main() {
    let _x: () = panic!();
    let _y: i32 = panic!();
    let _z: &dyn ToString = panic!();
}

However, we're using a bunch of type annotations above to manipulate the return type, in lieu of any type hints whatsoever Rust seems to settle on the default of () for expressions that return ! as shown by this simplified version of your example:

#![allow(unreachable_code)]

fn main() {
    let x = panic!();
    x + 5;
}

Which throws:

error[E0277]: cannot add `i32` to `()`
  --> src/main.rs:15:7
   |
15 |     x + 5;
   |       ^ no implementation for `() + i32`
   |
   = help: the trait `std::ops::Add<i32>` is not implemented for `()`

This seems like a reasonable choice given that empty expressions, such as an empty block, evaluate to the unit type.

In short: when you put a divergent function as the final statement in an expression and don't use any type annotations Rust infers the expression's return type to be (). This is why your error arm is inferred to return () and why you get the FromStr not implemented for () error.

like image 125
pretzelhammer Avatar answered Sep 19 '22 12:09

pretzelhammer