Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
zackw opened this issue May 13, 2023 · 7 comments
Open

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

zackw opened this issue May 13, 2023 · 7 comments
Labels
A-closures Area: Closures (`|…| { … }`) A-diagnostics Area: Messages for errors, warnings, and lints A-inference Area: Type inference C-bug Category: This is a bug. D-confusing Diagnostics: Confusing error or lint that should be reworked. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@zackw
Copy link
Contributor

zackw commented May 13, 2023

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).

@zackw zackw added the C-bug Category: This is a bug. label May 13, 2023
@Jules-Bertholet
Copy link
Contributor

@rustbot label A-inference

@rustbot rustbot added the A-inference Area: Type inference label May 16, 2023
@estebank
Copy link
Contributor

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
Copy link
Contributor

ickk commented Sep 11, 2024

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

@RodBurman
Copy link

Rustc 1.82.0 gives same output as 1.81.0.

@estebank estebank added A-closures Area: Closures (`|…| { … }`) T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Oct 25, 2024
@estebank
Copy link
Contributor

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.

@estebank estebank added A-diagnostics Area: Messages for errors, warnings, and lints D-confusing Diagnostics: Confusing error or lint that should be reworked. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Oct 25, 2024
@zackw
Copy link
Contributor Author

zackw commented Oct 25, 2024

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
Copy link
Contributor

@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.

jhpratt added a commit to jhpratt/rust that referenced this issue Jan 31, 2025
…il,compiler-errors

When encountering unexpected closure return type, point at return type/expression

```
error[E0271]: expected `{closure@fallback-closure-wrap.rs:18:40}` to be a closure that returns `()`, but it returns `!`
  --> $DIR/fallback-closure-wrap.rs:19:9
   |
LL |     let error = Closure::wrap(Box::new(move || {
   |                                        -------
LL |         panic!("Can't connect to server.");
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `!`
   |
   = note: expected unit type `()`
                   found type `!`
   = note: required for the cast from `Box<{closure@$DIR/fallback-closure-wrap.rs:18:40: 18:47}>` to `Box<dyn FnMut()>`
```

```
error[E0271]: expected `{closure@dont-ice-for-type-mismatch-in-closure-in-async.rs:6:10}` to be a closure that returns `bool`, but it returns `Option<()>`
  --> $DIR/dont-ice-for-type-mismatch-in-closure-in-async.rs:6:16
   |
LL |     call(|| -> Option<()> {
   |     ---- ------^^^^^^^^^^
   |     |          |
   |     |          expected `bool`, found `Option<()>`
   |     required by a bound introduced by this call
   |
   = note: expected type `bool`
              found enum `Option<()>`
note: required by a bound in `call`
  --> $DIR/dont-ice-for-type-mismatch-in-closure-in-async.rs:3:25
   |
LL | fn call(_: impl Fn() -> bool) {}
   |                         ^^^^ required by this bound in `call`
```

```
error[E0271]: expected `{closure@f670.rs:28:13}` to be a closure that returns `Result<(), _>`, but it returns `!`
    --> f670.rs:28:20
     |
28   |     let c = |e| -> ! {
     |             -------^
     |                    |
     |                    expected `Result<(), _>`, found `!`
...
32   |     f().or_else(c);
     |         ------- required by a bound introduced by this call
-Ztrack-diagnostics: created at compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs:1433:28
     |
     = note: expected enum `Result<(), _>`
                found type `!`
note: required by a bound in `Result::<T, E>::or_else`
    --> /home/gh-estebank/rust/library/core/src/result.rs:1406:39
     |
1406 |     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`
```

CC rust-lang#111539.
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Jan 31, 2025
Rollup merge of rust-lang#132156 - estebank:closure-return, r=Nadrieril,compiler-errors

When encountering unexpected closure return type, point at return type/expression

```
error[E0271]: expected `{closure@fallback-closure-wrap.rs:18:40}` to be a closure that returns `()`, but it returns `!`
  --> $DIR/fallback-closure-wrap.rs:19:9
   |
LL |     let error = Closure::wrap(Box::new(move || {
   |                                        -------
LL |         panic!("Can't connect to server.");
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `()`, found `!`
   |
   = note: expected unit type `()`
                   found type `!`
   = note: required for the cast from `Box<{closure@$DIR/fallback-closure-wrap.rs:18:40: 18:47}>` to `Box<dyn FnMut()>`
```

```
error[E0271]: expected `{closure@dont-ice-for-type-mismatch-in-closure-in-async.rs:6:10}` to be a closure that returns `bool`, but it returns `Option<()>`
  --> $DIR/dont-ice-for-type-mismatch-in-closure-in-async.rs:6:16
   |
LL |     call(|| -> Option<()> {
   |     ---- ------^^^^^^^^^^
   |     |          |
   |     |          expected `bool`, found `Option<()>`
   |     required by a bound introduced by this call
   |
   = note: expected type `bool`
              found enum `Option<()>`
note: required by a bound in `call`
  --> $DIR/dont-ice-for-type-mismatch-in-closure-in-async.rs:3:25
   |
LL | fn call(_: impl Fn() -> bool) {}
   |                         ^^^^ required by this bound in `call`
```

```
error[E0271]: expected `{closure@f670.rs:28:13}` to be a closure that returns `Result<(), _>`, but it returns `!`
    --> f670.rs:28:20
     |
28   |     let c = |e| -> ! {
     |             -------^
     |                    |
     |                    expected `Result<(), _>`, found `!`
...
32   |     f().or_else(c);
     |         ------- required by a bound introduced by this call
-Ztrack-diagnostics: created at compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs:1433:28
     |
     = note: expected enum `Result<(), _>`
                found type `!`
note: required by a bound in `Result::<T, E>::or_else`
    --> /home/gh-estebank/rust/library/core/src/result.rs:1406:39
     |
1406 |     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`
```

CC rust-lang#111539.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-closures Area: Closures (`|…| { … }`) A-diagnostics Area: Messages for errors, warnings, and lints A-inference Area: Type inference C-bug Category: This is a bug. D-confusing Diagnostics: Confusing error or lint that should be reworked. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

6 participants