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

Confusing compiler error for function taking closure returning reference #91966

Open
mgejke opened this issue Dec 15, 2021 · 3 comments
Open
Labels
A-closures Area: closures (`|args| { .. }`) A-diagnostics Area: Messages for errors, warnings, and lints A-lifetimes Area: lifetime related D-confusing Diagnostics: Confusing error or lint that should be reworked. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@mgejke
Copy link

mgejke commented Dec 15, 2021

Compiling the following code:

use std::collections::HashMap;

fn unclear_compiler_error(f: &dyn Fn(&i32) -> Option<&i32>) -> i32 {
    242
}

fn main() {
    let map: HashMap<i32, i32> = HashMap::new();

    let f = |p: &i32| match map.get(&p) {
        Some(v) => Some(v),
        _ => None,
    };
    unclear_compiler_error(&f);
}

Gives the following output:

error[E0308]: mismatched types
  --> src/compiler/main.rs:14:28
   |
14 |     unclear_compiler_error(&f);
   |                            ^^ one type is more general than the other
   |
   = note: expected enum `Option<&i32>`
              found enum `Option<&i32>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `thing` due to previous error

But I think the actual issue is with lifetimes, changing to Option<i32> makes it compile.

@Patrick-Poitras
Copy link
Contributor

fn unclear_compiler_error<'a>(f: &dyn Fn(&'a i32) -> Option<&i32>) -> i32 {
    242
}

Specifying the lifetime also makes it compile fine, so I think you're right that it seems to be a lifetime thing.

@BGR360
Copy link
Contributor

BGR360 commented Dec 21, 2021

Here's as minimal as I could get it (play):

fn func(_: &dyn Fn(&i32) -> &i32) {}

fn main() {
    let f = |_: &i32| &42;
    func(&f);
}
error[E0308]: mismatched types
 --> src/main.rs:5:10
  |
5 |     func(&f);
  |          ^^ one type is more general than the other
  |
  = note: expected reference `&i32`
             found reference `&i32`

But it gets weirder......

If you pass the closure inline, it works:

fn func(_: &dyn Fn(&i32) -> &i32) {}

fn main() {
    func(&|_: &i32| &42);
}

But if you make it reference a previously declared variable, it doesn't, and you get a different error message:

fn func(_: &dyn Fn(&i32) -> &i32) {}

fn main() {
    let x = 42;
    func(&|_: &i32| &x);
}
error[E0597]: `x` does not live long enough
 --> src/main.rs:5:22
  |
5 |     func(&|_: &i32| &x);
  |           --------- -^
  |           |         ||
  |           |         |borrowed value does not live long enough
  |           |         returning this value requires that `x` is borrowed for `'static`
  |           value captured here
6 | }
  | - `x` dropped here while still borrowed

You could try changing the function parameter from &dyn to impl, but that won't work:

fn func(_: impl Fn(&i32) -> &i32) {}

fn main() {
    let x = 42;
    func(|_: &i32| &x);
}
error[E0597]: `x` does not live long enough
 --> src/main.rs:5:21
  |
5 |     func(|_: &i32| &x);
  |          --------- -^
  |          |         ||
  |          |         |borrowed value does not live long enough
  |          |         returning this value requires that `x` is borrowed for `'static`
  |          value captured here
6 | }
  | - `x` dropped here while still borrowed

But if you move the closure out of line again, you unlock a brand new message!

fn func(_: impl Fn(&i32) -> &i32) {}

fn main() {
    let x = 42;
    let f = |_: &i32| &x;
    func(f);
}
error[E0308]: mismatched types
 --> src/main.rs:6:5
  |
6 |     func(f);
  |     ^^^^ lifetime mismatch
  |
  = note: expected reference `&i32`
             found reference `&i32`
note: the lifetime requirement is introduced here
 --> src/main.rs:1:29
  |
1 | fn func(_: impl Fn(&i32) -> &i32) {}
  |                             ^^^^

If you make the closure return the input directly, you get a little more information:

fn func(_: impl Fn(&i32) -> &i32) {}

fn main() {
    let f = |i: &i32| i;
    func(f);
}
error[E0308]: mismatched types
 --> src/main.rs:5:5
  |
5 |     func(f);
  |     ^^^^ lifetime mismatch
  |
  = note: expected reference `&i32`
             found reference `&i32`
note: the anonymous lifetime #1 defined here doesn't meet the lifetime requirements
 --> src/main.rs:4:13
  |
4 |     let f = |i: &i32| i;
  |             ^^^^^^^^^^^
note: the lifetime requirement is introduced here
 --> src/main.rs:1:29
  |
1 | fn func(_: impl Fn(&i32) -> &i32) {}
  |                             ^^^^

And if you move that closure inline..... IT WORKS?!

fn func1(_: impl Fn(&i32) -> &i32) {}
fn func2(_: &impl Fn(&i32) -> &i32) {}
fn func3(_: &dyn Fn(&i32) -> &i32) {}

fn main() {
    func1(|i: &i32| i);
    func2(&|i: &i32| i);
    func3(&|i: &i32| i);
}

This is pretty wacky.

@rustbot label +A-diagnostics +D-confusing +D-terse +T-compiler +A-lifetimes +A-closures

@rustbot rustbot added A-closures Area: closures (`|args| { .. }`) A-diagnostics Area: Messages for errors, warnings, and lints A-lifetimes Area: lifetime related D-confusing Diagnostics: Confusing error or lint that should be reworked. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Dec 21, 2021
@Aaron1011
Copy link
Member

Related: this code:

fn main() {
    let _closure = |s: &bool| s;
}

produces the following error:

error: lifetime may not live long enough
 --> src/main.rs:2:31
  |
2 |     let _closure = |s: &bool| s;
  |                        -    - ^ returning this value requires that `'1` must outlive `'2`
  |                        |    |
  |                        |    return type of closure is &'2 bool
  |                        let's call the lifetime of this reference `'1`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-closures Area: closures (`|args| { .. }`) A-diagnostics Area: Messages for errors, warnings, and lints A-lifetimes Area: lifetime related D-confusing Diagnostics: Confusing error or lint that should be reworked. D-terse Diagnostics: An error or lint that doesn't give enough information about the problem at hand. 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

5 participants