The main difference between these signatures boils down to the fact that !
can coerce into any other type, and thus is compatible with any other type (since this code path is never taken, we can assume it to be of any type we need). It's important when we have multiple possible code paths, such as if-else
or match
.
For example, consider the following (probably contrived, but hopefully clear enough) code:
fn assert_positive(v: i32) -> u32 {
match v.try_into() {
Ok(v) => v,
Err(_) => func(),
}
}
When func
is declared to return !
, this function compiles successfully. If we drop the return type, func
will be declared as returning ()
, and the compilation breaks:
error[E0308]: `match` arms have incompatible types
--> src/main.rs:8:19
|
6 | / match v.try_into() {
7 | | Ok(v) => v,
| | - this is found to be of type `u32`
8 | | Err(_) => func(),
| | ^^^^^^ expected `u32`, found `()`
9 | | }
| |_____- `match` arms have incompatible types
You can also compare this with definition for Result::unwrap
:
pub fn unwrap(self) -> T {
match self {
Ok(t) => t,
Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", &e),
}
}
Here, unwrap_failed
is returning !
, so it unifies with whatever type is returned in Ok
case.