How to Fix Borrowing Issue While Returning a Futures 0.3 Stream?

I have spent so long on this it is ridiculous, but I still can't figure this out. I am trying to port this function to Futures 0.3, but I'm getting a borrowing error. It is talking about the returned future/stream needing to live for a certain lifetime bound, but it isn't making sense to me because I'm moveing all of the data into the future so that it owns it. There shouldn't be any borrowing.

Here is me and a fellow contributor's attempt:

    pub fn all_crates(&self, query: Option<String>) -> impl Stream<Item = Result<Crate>> {
        let opts = ListOptions {
            query,
            sort: Sort::Alphabetical,
            per_page: 100,
            page: 1,
        };


        let c = self.clone();
        self.crates(opts.clone())
            .and_then(move |res| {
                let pages = (res.meta.total as f64 / 100.0).ceil() as u64;
                let streams_futures = (1..pages)
                    .map(|page| {
                        let opts = ListOptions {
                            page,
                            ..opts.clone()
                        };
                        c.crates(opts).and_then(|res| {
                            future::ok(stream::iter(res.crates.into_iter().map(|x| Ok(x))))
                        })
                    })
                    .collect::<Vec<_>>();
                let stream = stream::FuturesOrdered::from_iter(streams_futures).try_flatten();
                future::ok(stream)
            })
            .try_flatten_stream()
    }

And this is the error:

error: cannot infer an appropriate lifetime
   --> src/async_client.rs:297:14
    |
288 |     pub fn all_crates(&self, query: Option<String>) -> impl Stream<Item = Result<Crate>> {
    |                                                        --------------------------------- this return type evaluates to the `'static` lifetime...
...
297 |         self.crates(opts.clone())
    |         ---- ^^^^^^
    |         |
    |         ...but this borrow...
    |
note: ...can't outlive the anonymous lifetime #1 defined on the method body at 288:5
   --> src/async_client.rs:288:5
    |
288 | /     pub fn all_crates(&self, query: Option<String>) -> impl Stream<Item = Result<Crate>> {
289 | |         let opts = ListOptions {
290 | |             query,
291 | |             sort: Sort::Alphabetical,
...   |
314 | |             .try_flatten_stream()
315 | |     }
    | |_____^
help: you can add a bound to the return type to make it last less than `'static` and match the anonymous lifetime #1 defined on the method body at 288:5
    |
288 |     pub fn all_crates(&self, query: Option<String>) -> impl Stream<Item = Result<Crate>> + '_ {
    |                                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
   --> src/async_client.rs:330:35
    |
330 |             .and_then(move |cr| c.full_crate(&cr.name, all_versions))
    |                                   ^^^^^^^^^^
    |
note: first, the lifetime cannot outlive the lifetime `'_` as defined on the body at 330:23...
   --> src/async_client.rs:330:23
    |
330 |             .and_then(move |cr| c.full_crate(&cr.name, all_versions))
    |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that closure can access `c`
   --> src/async_client.rs:330:33
    |
330 |             .and_then(move |cr| c.full_crate(&cr.name, all_versions))
    |                                 ^
note: but, the lifetime must be valid for the method call at 329:9...
   --> src/async_client.rs:329:9
    |
329 | /         self.all_crates(query)
330 | |             .and_then(move |cr| c.full_crate(&cr.name, all_versions))
    | |_____________________________________________________________________^
note: ...so type `futures_util::stream::try_stream::and_then::AndThen<impl futures_core::stream::Stream, impl core::future::future::Future, [closure@src/async_client.rs:330:23: 330:69 c:async_client::Client, all_versions:bool]>` of expression is valid during the expression
   --> src/async_client.rs:329:9
    |
329 | /         self.all_crates(query)
330 | |             .and_then(move |cr| c.full_crate(&cr.name, all_versions))
    | |_____________________________________________________________________^

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0495`.
error: could not compile `crates_io_api`.

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

I've tried all kinds of stuff from boxing to async move blocks ( and of course taking the advice of the kind rust help message ), but nothing I do seems to get the stream to own its data. No matter what it still complains that that future is borrowing c.

It appears like self.crates borrows from self. You need to take ownership of whatever it returns. Additionally it seems like your errors about c are from outside the posted snippet.

Ah, yes, the second error is from another function that calls the function above. I changed the function above to call c.crates(options.clone()) instead of self.crates(opts.clone) which means it shouldn't need a reference to that anymore.

After going through a chain of attempts to follow the messages and eliminate borrowing I come to this:

pub fn all_crates(&self, query: Option<String>) -> impl Stream<Item = Result<Crate>> {
        let opts = ListOptions {
            query,
            sort: Sort::Alphabetical,
            per_page: 100,
            page: 1,
        };

        let c = self.clone();
        c.crates(opts.clone())
            .and_then(move |res| {
                let pages = (res.meta.total as f64 / 100.0).ceil() as u64;
                let streams_futures = (1..pages)
                    .map(move |page| {
                        let opts = ListOptions {
                            page,
                            ..opts.clone()
                        };
                        let c2 = c.clone();
                        c2.crates(opts).and_then(|res| {
                            future::ok(stream::iter(res.crates.into_iter().map(|x| Ok(x))))
                        })
                    })
                    .collect::<Vec<_>>();
                let stream = stream::FuturesOrdered::from_iter(streams_futures).try_flatten();
                future::ok(stream)
            })
            .try_flatten_stream()
    }

    pub fn all_crates_full(
        &self,
        query: Option<String>,
        all_versions: bool,
    ) -> impl Stream<Item = Result<FullCrate>> {
        let c = self.clone();
        self.all_crates(query).and_then(move |cr| {
            let c2 = c.clone();
            c2.full_crate(&cr.name, all_versions)
        })
    }

And the errors:

error[E0515]: cannot return value referencing local variable `c2`
   --> src/async_client.rs:307:25
    |
307 |                           c2.crates(opts).and_then(|res| {
    |                           ^-
    |                           |
    |  _________________________`c2` is borrowed here
    | |
308 | |                             future::ok(stream::iter(res.crates.into_iter().map(|x| Ok(x))))
309 | |                         })
    | |__________________________^ returns a value referencing data owned by the current function

error[E0597]: `c` does not live long enough
   --> src/async_client.rs:297:9
    |
288 |     pub fn all_crates(&self, query: Option<String>) -> impl Stream<Item = Result<Crate>> {
    |                                                        --------------------------------- opaque type requires that `c` is borrowed for `'static`
...
297 |         c.crates(opts.clone())
    |         ^ borrowed value does not live long enough
...
316 |     }
    |     - `c` dropped here while still borrowed
    |
help: you can add a bound to the opaque type to make it last less than `'static` and match `'static`
    |
288 |     pub fn all_crates(&self, query: Option<String>) -> impl Stream<Item = Result<Crate>> + 'static {
    |                                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0505]: cannot move out of `c` because it is borrowed
   --> src/async_client.rs:298:23
    |
288 |     pub fn all_crates(&self, query: Option<String>) -> impl Stream<Item = Result<Crate>> {
    |                                                        --------------------------------- opaque type requires that `c` is borrowed for `'static`
...
297 |         c.crates(opts.clone())
    |         - borrow of `c` occurs here
298 |             .and_then(move |res| {
    |                       ^^^^^^^^^^ move out of `c` occurs here
...
306 |                         let c2 = c.clone();
    |                                  - move occurs due to use in closure
    |
help: you can add a bound to the opaque type to make it last less than `'static` and match `'static`
    |
288 |     pub fn all_crates(&self, query: Option<String>) -> impl Stream<Item = Result<Crate>> + 'static {
    |                                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0515]: cannot return value referencing local variable `c2`
   --> src/async_client.rs:332:13
    |
332 |             c2.full_crate(&cr.name, all_versions)
    |             --^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |             |
    |             returns a value referencing data owned by the current function
    |             `c2` is borrowed here

error[E0515]: cannot return value referencing local data `cr.name`
   --> src/async_client.rs:332:13
    |
332 |             c2.full_crate(&cr.name, all_versions)
    |             ^^^^^^^^^^^^^^--------^^^^^^^^^^^^^^^
    |             |             |
    |             |             `cr.name` is borrowed here
    |             returns a value referencing data owned by the current function

error: aborting due to 5 previous errors

Some errors have detailed explanations: E0505, E0515, E0597.
For more information about an error, try `rustc --explain E0505`.
error: could not compile `crates_io_api`.

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

Can I see the signature of your crates function?

Sure:

pub async fn crates(&self, spec: ListOptions) -> Result<CratesResponse>

All async functions always borrow all references that are passed to them, which is why it borrows self.

1 Like

That's why I tried cloning self and then move-ing it into the closure:

 let c = self.clone();
        self.all_crates(query).and_then(move |cr| {
            let c2 = c.clone();
            c2.full_crate(&cr.name, all_versions)
        })

I also can't figure out why it worked with essentially the same design with Futures 0.1.

I'm feeling like the design itself needs to change somehow, but I don't know how yet. :thinking:

Even if you clone it, it's now a borrow of the local variable, which is even worse lifetime-wise. You probably want to have a non-borrowing version of the function.

1 Like

OK, so I switched the signatures of the crates and full_crate and to non-async functions that return impl Future<Output = _ > and then I moved the body of the function to an async move block that takes a clone of self and that actually worked! I don't know why that is much different than using an async fn, but like you said, apparently it borrows the references because it is an async function. That explains why it worked with Futures 0.1, because there was no such thing as async functions in the first implementation.

Anyway, whew, what a relief. I spent like all day yesterday on that. Thanks for the help!

Yeah. An async fn will never do any sort of clone like that — it will always borrow the references. In fact, when you call an async fn, it doesn't start running until you reach the first .await or something like that. It doesn't run anything inside at all until it's actually polled.

1 Like

OK, so when I made it return an impl Future instead, I was able to clone it because it wasn't borrowing self inside the returned future anymore. I now had control over what went into that returned future through the async move block.

That makes sense. That is doubtless going to help me in the future as I work with async stuff. Yay. :slight_smile:

Thanks again.

1 Like

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