Skip to content

Confusing diagnostic complains about lifetimes in a closure, when parameter type is missing #105675

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

Closed
mday64 opened this issue Dec 13, 2022 · 5 comments · Fixed by #105888
Closed
Assignees
Labels
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.

Comments

@mday64
Copy link

mday64 commented Dec 13, 2022

  1. Create a new project with cargo new closure_lifetimes
  2. cargo add pathfinding (in my case, it used pathfinding version 4.0.0 or 4.0.1)
  3. Replace main.rs with:
use std::collections::HashMap;
use pathfinding::prelude::bfs;

fn main() {
    // Just enough to satisfy the compiler
    let input = Input { ending_point: (3,4), heights: HashMap::new() };

    let success = |node| input.heights[node] == 0;
    let successors = |node| {
        let node_height = input.heights[node];
        input.neighbors(node).into_iter()
            .filter(|other| input.heights[&other] >= node_height - 1)
            .collect::<Vec<Coord>>()
    };
    let answer = bfs(&input.ending_point, successors, success).unwrap().len();
    println!("answer = {answer}");
}

type Coord = (i32, i32);

struct Input {
    ending_point: Coord,
    heights: HashMap<Coord, u32>
}

impl Input {
    // Just do something to satisfy the compiler
    fn neighbors(&self, node: &Coord) -> Vec<Coord> {
        vec![(node.0, node.1 + 1), (node.0 + 1, node.1)]
    }
}

cargo check produces the following output:

    Checking closure_lifetimes v0.1.0 (/Users/mark/sources/closure_lifetimes)
error[E0308]: mismatched types
  --> src/main.rs:15:18
   |
15 |     let answer = bfs(&input.ending_point, successors, success).unwrap().len();
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected trait `for<'r> FnMut<(&'r (i32, i32),)>`
              found trait `FnMut<(&(i32, i32),)>`
note: this closure does not fulfill the lifetime requirements
  --> src/main.rs:9:22
   |
9  |     let successors = |node| {
   |                      ^^^^^^
note: the lifetime requirement is introduced here
  --> /Users/mark/.cargo/registry/src/github.com-1ecc6299db9ec823/pathfinding-4.0.1/src/directed/bfs.rs:69:9
   |
69 |     FN: FnMut(&N) -> IN,
   |         ^^^^^^^^^^^^^^^

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:15:18
   |
15 |     let answer = bfs(&input.ending_point, successors, success).unwrap().len();
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&'2 (i32, i32)) -> Vec<(i32, i32)>` must implement `FnOnce<(&'1 (i32, i32),)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&'2 (i32, i32),)>`, for some specific lifetime `'2`

error[E0308]: mismatched types
  --> src/main.rs:15:18
   |
15 |     let answer = bfs(&input.ending_point, successors, success).unwrap().len();
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected trait `for<'r> FnMut<(&'r (i32, i32),)>`
              found trait `FnMut<(&(i32, i32),)>`
note: this closure does not fulfill the lifetime requirements
  --> src/main.rs:8:19
   |
8  |     let success = |node| input.heights[node] == 0;
   |                   ^^^^^^
note: the lifetime requirement is introduced here
  --> /Users/mark/.cargo/registry/src/github.com-1ecc6299db9ec823/pathfinding-4.0.1/src/directed/bfs.rs:71:9
   |
71 |     FS: FnMut(&N) -> bool,
   |         ^^^^^^^^^^^^^^^^^

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:15:18
   |
15 |     let answer = bfs(&input.ending_point, successors, success).unwrap().len();
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&'2 (i32, i32)) -> bool` must implement `FnOnce<(&'1 (i32, i32),)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&'2 (i32, i32),)>`, for some specific lifetime `'2`

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

For the two closures (success and successors), if I add a type for the parameter by changing |node| to |node: &Coord|, it builds without any errors. If it had told me that it needed type annotations, I would have understood the problem and been able to fix it right away. (I assumed that there was enough type information for it to infer the correct types.)

The first confusing bit is that both errors highlight the entire call to bfs(), including all parameters. This makes it harder to figure out which one it is complaining about (though the second note contains the line number). Does the first argument have something to do with the lifetimes it is complaining about?

I found this part of the message hard to understand:

   = note: closure with signature `fn(&'2 (i32, i32)) -> Vec<(i32, i32)>` must implement `FnOnce<(&'1 (i32, i32),)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&'2 (i32, i32),)>`, for some specific lifetime `'2`

The type parameters look almost identical, and the for some specific lifetime didn't make sense to me. Could it somehow indicate the bounds of that lifetime (2), or better explain why that lifetime doesn't last long enough?

FYI: found as part of solving Advent of Code 2022, Day 12.

@mday64 mday64 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 Dec 13, 2022
@skyzh
Copy link
Contributor

skyzh commented Dec 18, 2022

A smaller MCVE:

pub fn bfs<N, FN, IN, FS>(start: &N, successors: FN, success: FS) -> Option<Vec<N>>
where
    FN: FnMut(&N) -> IN,
    IN: IntoIterator<Item = N>,
    FS: FnMut(&N) -> bool,
{
    None
}

struct Test;

fn main() {
    let success = |node| true;
    let successors = |node| {
        vec![]
    };
    let answer = bfs(&Test, successors, success).unwrap().len();
    println!("answer = {answer}");
}

@skyzh
Copy link
Contributor

skyzh commented Dec 18, 2022

I'm thinking whether we should fix this as a compiler bug (i.e., make it compile) or suggesting people write explicit type here 🤣

@skyzh
Copy link
Contributor

skyzh commented Dec 19, 2022

maybe related: #105528

@skyzh
Copy link
Contributor

skyzh commented Dec 19, 2022

Also if we move the closure to the parameter:

fn main() {
    let answer = bfs(&Test, |node| vec![],  |node| true).unwrap().len();
    println!("answer = {answer}");
}

This will also compile. Combined with #105528, I guess something is wrong with propagating lifetime of closures when we store it in a variable...

@skyzh
Copy link
Contributor

skyzh commented Dec 19, 2022

@rustbot claim

Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Apr 14, 2023
…re, r=compiler-errors

suggest lifetime for closure parameter type when mismatch

This is a draft PR, will add test cases later and be ready for review.

This PR fixes rust-lang#105675 by adding a diagnostics suggestion. Also a partial fix to rust-lang#105528.

The following code will have a compile error now:

```
fn const_if_unit(input: bool) -> impl for<'a> FnOnce(&'a ()) -> usize {
    let x = |_| 1;
    x
}
```

Before this PR:

```
error[E0308]: mismatched types
 --> src/lib.rs:3:5
  |
3 |     x
  |     ^ one type is more general than the other
  |
  = note: expected trait `for<'a> FnOnce<(&'a (),)>`
             found trait `FnOnce<(&(),)>`
note: this closure does not fulfill the lifetime requirements
 --> src/lib.rs:2:13
  |
2 |     let x = |_| 1;
  |             ^^^

error: implementation of `FnOnce` is not general enough
 --> src/lib.rs:3:5
  |
3 |     x
  |     ^ implementation of `FnOnce` is not general enough
  |
  = note: closure with signature `fn(&'2 ()) -> usize` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2`

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

After this PR:

```
error[E0308]: mismatched types
 --> src/lib.rs:3:5
  |
3 |     x
  |     ^ one type is more general than the other
  |
  = note: expected trait `for<'a> FnOnce<(&'a (),)>`
             found trait `FnOnce<(&(),)>`
note: this closure does not fulfill the lifetime requirements
 --> src/lib.rs:2:13
  |
2 |     let x = |_| 1;
  |             ^^^
help: consider changing the type of the closure parameters
  |
2 |     let x = |_: &_| 1;
  |             ~~~~~~~

error: implementation of `FnOnce` is not general enough
 --> src/lib.rs:3:5
  |
3 |     x
  |     ^ implementation of `FnOnce` is not general enough
  |
  = note: closure with signature `fn(&'2 ()) -> usize` must implement `FnOnce<(&'1 (),)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 (),)>`, for some specific lifetime `'2`

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

After applying the suggestion, it compiles. The suggestion might not always be correct as the generation procedure of that suggestion is quite simple...
@bors bors closed this as completed in 2a71115 Apr 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants