Hello all,
I've a confusing lifetime issue that I hope you can help me with.
pub fn find_items<F>(input: &str, checker: F) -> impl Iterator<Item = usize>
where
F: Fn(usize) -> bool,
{
let range = (0..42);
range.filter(|id| checker(*id))
}
The function above produces this error below, which says a lifetime bound needs to be added for checker for it to be valid for the lifetime of input. This is confusing since even though the function takes input, it never uses it. And I'm unsure if the fix suggested here is even the appropriate one.
error[E0311]: the parameter type `F` may not live long enough
--> day-02-gift-shop/src/lib.rs:39:5
|
34 | pub fn find_items<F>(input: &str, checker: F) -> impl Iterator<Item = usize>
| ---- the parameter type `F` must be valid for the anonymous lifetime defined here...
...
39 | range.filter(|id| checker(*id))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `F` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound
|
34 ~ pub fn find_items<'a, F>(input: &'a str, checker: F) -> impl Iterator<Item = usize>
35 | where
36 ~ F: Fn(usize) -> bool + 'a,
|
I think the cause is that the Filter iterator returned by .filter might hold the predicate provided to it, i.e., the closure |id| checker(*id). The closure captures checker by reference because checker is of type Fn and calling an Fn only requires a &self. So the fix is to tell the closure to take checker by value by using .filter(move |id| checker(*id)).
This is either a bug or I'm missing something, which if true I would greatly appreciate if you could help point it out.
the impl Trait lifetime capturing rule changed to "capturing all lifetimes by default" in edition 2024:
you need to add explicit use<...> capture bound:
-pub fn find_items<F>(input: &str, checker: F) -> impl Iterator<Item = usize>
+pub fn find_items<F>(input: &str, checker: F) -> impl Iterator<Item = usize> + use<>
but then you will get an error aoubt F, so you need to add F in the capture list, and then, you also need to move the checker into the closure to fix a borrow checker error, so the final version looks like this:
-pub fn find_items<F>(input: &str, checker: F) -> impl Iterator<Item = usize>
+pub fn find_items<F>(input: &str, checker: F) -> impl Iterator<Item = usize> + use<F>
where
F: Fn(usize) -> bool,
{
let range = (0..42);
- range.filter(|id| checker(*id))
+ range.filter(move |id| checker(*id))
}
2 Likes
That's correct. Just adding move like the second error suggests is the solution.
The first diagnostic is misleading. It would be better if it just wasn't emitted. So I would say that's a diagnostic issue.
Let's name things so I can talk about what I think is going on:
// Same signature with less elision
pub fn find_items<'s, F>(
input: &'s str,
checker: F,
) -> impl Iterator<Item = usize> + use<'s, F>
where
F: Fn(usize) -> bool,
I believe what is going on is that you have some impl Iterator<Item = usize> + use<'bor, F> in your function body, where 'bor is some borrow duration for checker (the closure holds a &'bor checker). This can only satisfy the impl ... + use<'s, F> from the signature if 'bor: 's. Effectively you would need to create a &'s checker for the closure to capture.
There are two problems with this:
- You can never borrow a local variable for a caller-supplied lifetime like
's
- The caller may provide some
F where F: 's doesn't hold, so &'s F is an invalid type
The E309 is talking about this second problem. But the first problem makes it irrelevant: borrowing the local is never going to work. Moving checker into the closure is the correct solution.
You don't necessarily need + use<F>. If you add + use<F>, your iterator won't be able to capture input. On the other hand, if you know you will never need to do that that, it is nicer to the caller to add + use<F>, since the iterator will no longer keep *input borrowed.
1 Like
Thank you for your explanation.
This makes it clearer why it suggested that F must be + 's: it is captured by the hidden type, and the opaque type in the signature implicitly captures 's.
You're right that calling it a bug might not be appropriate, as it is more of a diagnostic issue. I apologize for the misuse of the term.