Trouble resolving lifetime issue with await

Hi folks,

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_things async 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;
}

(Playground)

Errors:

   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

I don't really follow this sentence.

Anyway, you can just shorten the lifetime manually:

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(|a| a));
    
    do_things(new_iter);
}

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
}
4 Likes

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.

1 Like