! in Fn types

ad71887
Opened by Karol Kuczmarski at 2024-12-21 05:09:57

I've encountered the following issue (playpen link):

use std::process::exit;

fn main() {
    let stuff = get_stuff().unwrap_or_else(exit);  // ERROR
    let stuff = get_stuff().unwrap_or_else(|e| exit(e));  // WORKS
    let stuff = get_stuff().unwrap_or_else(|e| { exit(e); }); // ALSO WORKS
}

type Stuff = u32;
type ExitCode = i32;

fn get_stuff() -> Result<Stuff, ExitCode> {
    Ok(42)
}

Conceptually, all three variants should do the exact same thing, but the first one yields the following error:

error[E0271]: type mismatch resolving `<fn(i32) -> ! {std::process::exit} as std::ops::FnOnce<(i32,)>>::Output == u32`
 --> <anon>:5:29
  |
5 |     let stuff = get_stuff().unwrap_or_else(exit);
  |                             ^^^^^^^^^^^^^^ expected !, found u32
  |
  = note: expected type `!`
             found type `u32`

which seems to be about the lack of ! to u32 conversion. But the second variant is where exactly such a conversion appears to be occurring. Furthermore, the third variant indicates even more exceptional behavior regarding !, as it seemingly enables () to be treated as the Stuff type.

Is this an intended behavior, a type inference bug, unclear error messaging, or something else entirely?

  1. Coercions of types not-at-top level do not happen. For example this does not work either:

    #![feature(never_type)]
    fn foo(x: Option<u32>) { }
    fn bar() {
        let x: Option<!> = None;
        foo(x);
    }
    

    Furthermore, the third variant indicates even more exceptional behavior regarding !, as it seemingly enables () to be treated as the Stuff type.

    I’m not sure what you mean there. For both the 2nd and 3rd line the return type of the closure is u32. The third closure typechecks fine for the same reason

    fn foo() -> u32 { exit(1); }
    

    does.

    Simonas Kazlauskas at 2017-04-08 15:37:47

  2. I believe this intended behavior. Should this be closed?

    bjorn3 at 2022-05-15 16:38:15