Type-checking ! vs i32

This doesn't compile, but it seems like it should:

fn panic() -> ! { panic!("foo") }
fn f(x: Option<i32>) -> i32 { x.unwrap_or_else(panic) }
  |
2 | fn f(x: Option<i32>) -> i32 { x.unwrap_or_else(panic) }
  |                                 ^^^^^^^^^^^^^^ expected `!`, found `i32`
  |
  = note: expected type `!`
             found type `i32`

Shouldn't FnOnce() -> ! match FnOnce() -> i32?

No, it shouldn't. They are different traits, and the never type only let's you coerce between different types

You can do something like the following if y9u want it to compile

fn panic<T>() -> T { panic!("foo") }
fn f(x: Option<i32>) -> i32 { x.unwrap_or_else(panic) }

This also works:

fn panic() -> ! { panic!("foo") }
fn f(x: Option<i32>) -> i32 { x.unwrap_or_else(|| panic()) }

The original code doesn't compile because type coercion only happens on the top level, not in the type parameters. For example, &String coerces to &str, but Option<&String> doesn't coerce to Option<&str>, and Fn(&String) doesn't coerce to Fn(&str).

I still don't really understand. According to [Subtyping and Variance - The Rust Reference]:

fn()->T is covariant in T, which as defined there implies that if T is a subtype of U, then fn()->T is a subtype of fn()->U.

! is a subtype of i32, which should imply that the function panic, being of type fn()->!, should also be considered to be (a subtype) of type fn()->i32.

panic is not of type fn() -> !. It has an anonymous type, which happens to implement Fn() -> ! (note the capitalization). It does not implement Fn() -> i32, which is the trait requirement of unwrap_or_else. panic can be coerced to fn() -> !, but even if you do that it will still not implement Fn() -> i32 because they are different traits.

Moreover, ! is also not a subtype of i32. ! is coercible to i32, but that does not make a subtype relationship (previous discussion about this topic). For essentially the same reason, &String is coercible to &str, but &&String is not coercible to &&str.

Subtyping in Rust has to do with lifetimes. fn() -> &'static str is a subtype of fn() -> &'a str (actually that last one is hard to express, but you get the point). No lifetimes = no subtyping.

No, subtyping only applies to lifetimes. All other subtyping-like relations are merely conversions that the language support, and they are not subject to covariance.