Understanding strange discrepancy with Never type and closures

Consider the following simple program:

use std::process;
use std::fmt::Display;

fn fatal(message: impl Display) -> ! {
    eprintln!("{}", message);
    process::exit(1);
}

fn main() {
    let x: Result<i32, &str> = Err("An error occurred");
    println!("{}", x.unwrap_or_else(|err| fatal(err)));
}

It compiles, and running it produces the result of "An error occurred" being output to standard error, as expected.

However if I make the following ever-so-slight change to the program, changing main to be:

fn main() {
    let x: Result<i32, &str> = Err("An error occurred");
    println!("{}", x.unwrap_or_else(fatal));
}

Then the program fails to compile, giving the following error message:

error[E0271]: type mismatch resolving `<fn(&str) -> ! {fatal::<&str>} as FnOnce<(&str,)>>::Output == i32`
  --> src/main.rs:11:22
   |
11 |     println!("{}", x.unwrap_or_else(fatal));
   |                      ^^^^^^^^^^^^^^ expected `!`, found `i32`
   |
   = note: expected type `!`
              found type `i32`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0271`.

I'm having trouble making sense of this. In the first version of the program (i.e. the one that compiles successfully) the closure returns a value of type Never, which being the bottom-type, unifies with the otherwise expected i32. This makes perfect sense. What is confusing to me is that despite (as far as I can tell) the second version of the program (i.e. the one that does not compile successfully) also producing a value of type Never from unwrap_or_else, just without the seemingly useless closure, it somehow fails to compile due to a type mismatch. I would greatly appreciate it if anyone could shed some light on this for me.

1 Like

In the first case, a coercion from ! to i32 occurs. This is possible because the return type of the closure is inferred, and it is being inferred to be i32, given that its body diverges.

In the second case, there's no such possibility: the return type of the function is literal !, not i32.

This could work in theory by automatically inserting the unnecessary closure so that the coercion can happen. It could also be fixed by adding type-level (inheritance-like) subtyping to Rust, but I find that overwhelmingly unlikely.

1 Like

The closure returns i32. It is inside the closure where never type is implicitly coerced into i32 - this is possible only with literal never, not with some type containing it.

1 Like

Thank you for the explanation, this is subtle, but definitely makes sense.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.