Borrow self inside try_unfold() closure

Hi,
How do I resolve lifetime error below when using self inside closure of try_unfold()?
Thanks in advance.

use futures::stream::{self, Stream};
use std::error::Error;

fn main() {
}

fn test_stream() {
    struct Client {}
    impl Client {
        pub fn hello(&self, name: &str) {
            println!("Hello {}", name);
        }

        pub async fn get_names(
            &self,
            path: &str,
        ) -> Box<dyn Stream<Item = Result<i32, Box<dyn Error>>>> {
            Box::new(stream::try_unfold(0, |state| async move {
                self.hello("World");
                Ok(Some((0, 0)))
            }))
        }
    }
}


(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error: cannot infer an appropriate lifetime
  --> src/main.rs:15:13
   |
15 |               &self,
   |               ^^^^^ ...but this borrow...
...
18 | /             Box::new(stream::try_unfold(0, |state| async move {
19 | |                 self.hello("World");
20 | |                 Ok(Some((0, 0)))
21 | |             }))
   | |_______________- this return type evaluates to the `'static` lifetime...
   |
note: ...can't outlive the lifetime `'_` as defined on the method body at 15:13
  --> src/main.rs:15:13
   |
15 |             &self,
   |             ^

error: aborting due to previous error

error: could not compile `playground`.

To learn more, run the command again with --verbose.

Box<dyn Stream<...>> is actually something like Box<dyn 'static + Stream<...>>, so if you explicitly elide the lifetime, like so Box<dyn '_ + Stream<Item = Result<i32, Box<dyn Error>>>>, Rust will infer the correct lifetimes.

In this case since two of the arguments are references, it doesn't seem like it can infer them. Since the path is not used, the correct way to tell it that the stream contains references to self but not path would be:

pub fn get_names<'a>(
    &'a self,
    path: &str,
) -> impl Stream<Item = Result<i32, Box<dyn Error>>> + 'a {
    stream::try_unfold(0, move |state| async move {
        self.hello("World");
        Ok(Some((0, 0)))
    })
}

By adding &'a str, you say that path is also stored inside the stream. Note that I changed it to impl Stream, which I generally recommend for this kind of usage.

Thanks @RustyYato I changed like that but I got another error:

`self` does not live long enough

Playground

Hi Alice, actually path is used, sorry I missed that in my previous code. Without path usage it compiles OK with your suggestion but when I use path it errors again.
I've updated playground to use path, I've also tried to add lifetime for path as well but it still doesn't compile:
Playground

   Compiling playground v0.0.1 (/playground)
error[E0373]: closure may outlive the current function, but it borrows `path`, which is owned by the current function
  --> src/main.rs:18:35
   |
18 |             stream::try_unfold(0, |state| async move {
   |                                   ^^^^^^^ may outlive borrowed value `path`
19 |                 self.hello(path);
   |                            ---- `path` is borrowed here
   |
note: closure is returned here
  --> src/main.rs:18:13
   |
18 | /             stream::try_unfold(0, |state| async move {
19 | |                 self.hello(path);
20 | |                 Ok(Some((0, 0)))
21 | |             })
   | |______________^
help: to force the closure to take ownership of `path` (and any other referenced variables), use the `move` keyword
   |
18 |             stream::try_unfold(0, move |state| async move {
   |                                   ^^^^^^^^^^^^

error[E0373]: closure may outlive the current function, but it borrows `self`, which is owned by the current function
  --> src/main.rs:18:35
   |
18 |             stream::try_unfold(0, |state| async move {
   |                                   ^^^^^^^ may outlive borrowed value `self`
19 |                 self.hello(path);
   |                 ---- `self` is borrowed here
   |
note: closure is returned here
  --> src/main.rs:18:13

You need to add move to the closure too, like you see in my example.

2 Likes

Yes, like @alice said, if you add move to your closure it will compile.

You can read about why in my blog

1 Like

Thanks @alice , @RustyYato , it works now.

BTW @alice can you elaborate this a bit more?

Using the impl Trait syntax you avoid an allocation and Rust is better able to optimize your code. So it's usually better to use it where you can

That makes sense, thanks @RustyYato .
I've also looked at your link about closure, it's very helpful :+1:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.