I currently have some issues with the following piece of code and I don't 100% percent understand why I get lifetime issues with it. To my understanding, the new_iter in prepend_to_do_things borrows value.
I can see, that this would generally be an issue "if" the async function call would not be waited for, as this could lead to a dangling reference to a stack-local variable. In the presented case, however, we should be able to guarantee that the do_thingsasync and thus the reference will be gone, as well, or is there an issue with my reasoning?
Edit: dropped comment from code example
use std::iter;
async fn do_things<'a, I>(test_iter: I)
where I: IntoIterator<Item = &'a u32>
{
async {
let values: Vec<_> = test_iter.into_iter().collect();
println!("{:#?}", values);
}.await
}
async fn prepend_to_do_things<'a, I>(test_iter: I)
where I: IntoIterator<Item = &'a u32>
{
let value: u32 = 42;
let new_iter = iter::once(&value).chain(test_iter);
do_things(new_iter).await
}
#[tokio::main]
async fn main() {
let values = vec![1,2,3,4,5];
prepend_to_do_things(&values).await;
}
Compiling playground v0.0.1 (/playground)
error[E0597]: `value` does not live long enough
--> src/main.rs:18:31
|
13 | async fn prepend_to_do_things<'a, I>(test_iter: I)
| -- lifetime `'a` defined here
...
18 | let new_iter = iter::once(&value).chain(test_iter);
| -----------^^^^^^------------------
| | |
| | borrowed value does not live long enough
| argument requires that `value` is borrowed for `'a`
...
21 | }
| - `value` dropped here while still borrowed
For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` due to previous error
To understand how this goes wrong, let me insert some extra lifetime-annotations (this doesn't compile, but is just to illustrate the point):
async fn prepend_to_do_things<'long, I>(test_iter: I)
where
I: IntoIterator<Item = &'long u32>,
{
let value: u32 = 42;
let value_ref: &'short u32 = &value;
let new_iter = iter::once(value_ref).chain(test_iter);
do_things(new_iter).await
}
Since the chain method requires that the item type of an iterator are the same, you now require that &'long u32 = &'short u32, which implies that 'short = 'long. This is not the case. Normally the compiler would auto-shorten the lifetime for you, but it isn't willing to do that for generics.
To fix it, insert a .map(|r| r) operation. The compiler will auto-shorten the lifetime on r from 'long to 'short to make the lifetimes match.
async fn prepend_to_do_things<'a, I>(test_iter: I)
where
I: IntoIterator<Item = &'a u32>,
{
let value: u32 = 42;
let new_iter = iter::once(&value).chain(test_iter.into_iter().map(|r| r));
do_things(new_iter).await
}
Thanks for the quick help (same goes for @zirconium-n who sadly had to struggle through my hurried post, sorry for that!).
So let me try to put it in my own words to make sure I understood you correctly: the issue is, that the elements that the iterator that prepend_to_do_things gets as an argument have a longer lifetime (one that comes from the calling function) than the lifetime of value which is defined only locally. These lifetimes are not compatible.
I still have some trouble to understand why the map(|a| a) now helps to resolve this issue, though. Is the lifetime of new_iter in my example per chain(...)'long (to use your terminology)? Then my suspicion would be, that the map(|a| a) causes the compiler to auto-shorten the lifetime as the Map object is created in the current stack frame. Is this reasoning correct here?
Any copy or move of a reference actually involves an implicit shorten-the-lifetime operation as part of the copy. So, the .map(|a| a) operation can replace the lifetime on a with any shorter lifetime since it copies the reference.