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

Ambiguous output when higher rank trait bound is not met #95182

Closed
jasta opened this issue Mar 21, 2022 · 3 comments
Closed

Ambiguous output when higher rank trait bound is not met #95182

jasta opened this issue Mar 21, 2022 · 3 comments
Labels
A-async-await Area: Async & Await A-diagnostics Area: Messages for errors, warnings, and lints A-higher-ranked Area: Higher-ranked things (e.g., lifetimes, types, trait bounds aka HRTBs) A-lifetimes Area: Lifetimes / regions T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@jasta
Copy link

jasta commented Mar 21, 2022

Given the following code demonstrating an invalid lifetime mismatch between the borrowed whom argument and the returned future:

use std::future::Future;

#[tokio::main]
async fn main() {
  do_say_hi(my_func).await;
}

async fn my_func(whom: &str) {
  println!("hi {whom}");
}

async fn do_say_hi<F, R>(func: F)
where
F: Fn(&str) -> R,
R: Future<Output = ()> {
  (func)("world").await;
}

Playground for more context: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=58bcba6782e856d0de5f3d1271bf2c9e

The output from rustc makes a strange claim about a mismatched type with the expected and found type labels being identical:

   = note: expected associated type `<for<'_> fn(&str) -> impl Future<Output = ()> {my_func} as FnOnce<(&str,)>>::Output`
              found associated type `<for<'_> fn(&str) -> impl Future<Output = ()> {my_func} as FnOnce<(&str,)>>::Output`
   = note: the required lifetime does not necessarily outlive the empty lifetime

I believe the underlying issue is simply that the whom reference does not necessarily outlive the future that it returns. That is, by the time the future is awaited, it could be beyond the lifetime of whom. The compiler is correct to halt on this code, however, the error message is where I think the bug really lies as it asks the developer to character-by-character compare an identical string only to guess at what the real issue is. Further, changing this code very slightly reveals a greatly improved error message by simply using closure syntax instead of the named fn:

  do_say_hi(|whom| async move {
    println!("hi {whom}");
  }).await;

Rust playground for the revised sample: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f2ea2a2834215e0acdc6b88de3b70471

Now the error message we get is detailed and helpful:

5 |     do_say_hi(|whom| async move {
  |  ______________-----_^
  | |              |   |
  | |              |   return type of closure `impl Future<Output = [async output]>` contains a lifetime `'2`
  | |              has type `&'1 str`
6 | |     println!("hi {whom}");
7 | |   }).await;
  | |___^ returning this value requires that `'1` must outlive `'2`

EDIT: I discovered a much simpler example that well highlights the poor error message as opposed to the underlying and very real lifetime issue that existed in my code.

@jasta jasta added A-diagnostics Area: Messages for errors, warnings, and lints T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Mar 21, 2022
@jasta
Copy link
Author

jasta commented Mar 24, 2022

In either case the code can be very simply fixed by modifying do_say_hi as such:

async fn do_say_hi<'a, F, R>(func: F)
where
F: Fn(&'a str) -> R,
R: Future<Output = ()> + 'a {
  (func)("world").await;
}

But the issue again is that the compiler makes that fix very clear in the second example, but extremely ambiguous in the first.

@fmease
Copy link
Member

fmease commented Sep 24, 2024

Triage:

Current Output
error[E0308]: mismatched types
  --> src/main.rs:5:3
   |
5  |   do_say_hi(my_func).await;
   |   ^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected opaque type `impl for<'a> Future<Output = ()>`
              found opaque type `impl Future<Output = ()>`
   = help: consider `await`ing on both `Future`s
   = note: distinct uses of `impl Trait` result in different opaque types
note: the lifetime requirement is introduced here
  --> src/main.rs:14:16
   |
14 | F: Fn(&str) -> R,
   |                ^

error: implementation of `Fn` is not general enough
  --> src/main.rs:5:3
   |
5  |     do_say_hi(my_func).await;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^
...
12 | / async fn do_say_hi<F, R>(func: F)
13 | | where
14 | | F: Fn(&str) -> R,
   | |    ------------- doesn't satisfy where-clause
15 | | R: Future<Output = ()> {
   | |______________________- due to a where-clause on `do_say_hi`...
   |
   = note: ...`for<'a> fn(&'a str) -> impl Future<Output = ()> {my_func}` must implement `Fn<(&str,)>`
   = note: ...but it actually implements `Fn<(&'0 str,)>`, for some specific lifetime `'0`

error: implementation of `Fn` is not general enough
  --> src/main.rs:5:22
   |
5  |     do_say_hi(my_func).await;
   |                        ^^^^^
...
12 | / async fn do_say_hi<F, R>(func: F)
13 | | where
14 | | F: Fn(&str) -> R,
   | |    ------------- doesn't satisfy where-clause
15 | | R: Future<Output = ()> {
   | |______________________- due to a where-clause on `do_say_hi`...
   |
   = note: ...`for<'a> fn(&'a str) -> impl Future<Output = ()> {my_func}` must implement `Fn<(&str,)>`
   = note: ...but it actually implements `Fn<(&'0 str,)>`, for some specific lifetime `'0`

@fmease fmease added A-lifetimes Area: Lifetimes / regions A-async-await Area: Async & Await A-higher-ranked Area: Higher-ranked things (e.g., lifetimes, types, trait bounds aka HRTBs) labels Sep 24, 2024
@jasta jasta closed this as completed Sep 25, 2024
@jasta
Copy link
Author

jasta commented Sep 25, 2024

I feel satisfied this issue is resolved given the new output, thanks for following up!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-async-await Area: Async & Await A-diagnostics Area: Messages for errors, warnings, and lints A-higher-ranked Area: Higher-ranked things (e.g., lifetimes, types, trait bounds aka HRTBs) A-lifetimes Area: Lifetimes / regions T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

2 participants