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.
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.
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