rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.23k stars 12.7k forks source link

Type inference doesn't properly handle closures that don't return #111539

Open zackw opened 1 year ago

zackw commented 1 year ago

In the code below, both of the demo_* functions provoke type inference errors that I believe should not happen.

use std::error::Error;
use std::process::exit;

fn demo_one<F>(f: F) -> ()
where
    F: FnOnce() -> Result<(), Box<dyn Error>>,
{
    f().or_else(|e| {
        eprintln!("{:?}", e);
        exit(1)
    });
}

fn demo_two<F>(f: F) -> ()
where
    F: FnOnce() -> Result<(), Box<dyn Error>>,
{
    f().or_else(|e| -> ! {
        eprintln!("{:?}", e);
        exit(1)
    });
}

The errors I observe are (suggestions omitted for space):

error[E0282]: type annotations needed for `Result<(), F>`
 --> src/lib.rs:8:17
8 |     f().or_else(|e| {
error[E0271]]: expected `[closure@lib.rs:18:17]` to be a closure that returns `Result<(), _>`, but it returns `!`
  --> src/lib.rs:18:17
18 |       f().or_else(|e| -> ! {

I believe these errors to be incorrect, because:

  1. Both closures end with a call to std::process::exit, which does not return. The return type of the closure in demo_one should therefore have been inferred to be !, matching the closure in demo_two. (Note: whether that call has a ; afterward does not make any difference.)
  2. As documented in https://doc.rust-lang.org/std/primitive.never.html, ! coerces to any type, therefore a callable which never returns ought to be compatible with a caller expecting any return type.

Meta

rustc --version --verbose:

rustc 1.69.0 (84c898d65 2023-04-16)
binary: rustc
commit-hash: 84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc
commit-date: 2023-04-16
host: aarch64-unknown-linux-gnu
release: 1.69.0
LLVM version: 15.0.7

Identical behavior observed from nightly (playground link).

Jules-Bertholet commented 1 year ago

@rustbot label A-inference

estebank commented 1 year ago

Current output:


error[E0282]: type annotations needed for `Result<(), F>`
 --> f71.rs:8:17
  |
8 |     f().or_else(|e| {
  |                 ^^^
  |
help: try giving this closure an explicit return type
  |
8 |     f().or_else(|e| -> Result<(), F> {
  |                     ++++++++++++++++

error[E0271]: expected `[closure@f71.rs:18:17]` to be a closure that returns `Result<(), _>`, but it returns `!`
    --> f71.rs:18:17
     |
18   |       f().or_else(|e| -> ! {
     |  _________-------_^
     | |         |
     | |         required by a bound introduced by this call
19   | |         eprintln!("{:?}", e);
20   | |         exit(1)
21   | |     });
     | |_____^ expected `Result<(), _>`, found `!`
     |
     = note: expected enum `Result<(), _>`
                found type `!`
note: required by a bound in `Result::<T, E>::or_else`
    --> /home/gh-estebank/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:1379:39
     |
1379 |     pub fn or_else<F, O: FnOnce(E) -> Result<T, F>>(self, op: O) -> Result<T, F> {
     |                                       ^^^^^^^^^^^^ required by this bound in `Result::<T, E>::or_else`
ickk commented 1 month ago

I'm running into this same issue with a closure that calls panic! with rustc 1.81.0 (eeb90cda1 2024-09-04)

RodBurman commented 1 week ago

Rustc 1.82.0 gives same output as 1.81.0.

estebank commented 1 week ago

Note that the way to write this is by making the closures return Result<(), Box<dyn Error> and make their bodies be ! (which in both cases they already are). I don't think this is necessarily a limitation of the type system/inference (although an RFC could be written to make Fn() -> ! be accepted anywhere for<T> Fn() -> T is), but rather bad compiler diagnostics.

zackw commented 1 week ago

https://doc.rust-lang.org/std/primitive.never.html says ! coerces to any type, so I think this is a type system/inference issue; it should not be necessary to give these closures a return type; whatever the compiler thinks the closure's return type needs to be, ! should satisfy it.

estebank commented 1 week ago

@zackw even when ! is the explicit return type? I can buy the argument that a closure with inferred ! return type should be accepted, but the explicit case isn't as clear to me.