futures-rs icon indicating copy to clipboard operation
futures-rs copied to clipboard

Function parameter lifetime in `StreamExt::filter` generic isn't good enough

Open ShadowJonathan opened this issue 4 years ago • 2 comments

I have this following call on a stream, to filter out elements asynchronously (sorry for the ugly encapsulation, async closures aren't a thing yet);

.filter(move |user_id| Box::pin(async move {!db.users.is_deactivated(user_id).await.unwrap_or(false)}))

The important part here is that .is_deactivated takes a borrowed UserId type, which is taken across await boundaries with the subsequent .await

However, rust complains about this...

error: lifetime may not live long enough
   --> src/database/rooms.rs:802:36
    |
802 |             .filter(move |user_id| Box::pin(async move {!db.users.is_deactivated(user_id).await.unwrap_or(false)}))
    |                           -------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
    |                           |      |
    |                           |      return type of closure `Pin<Box<impl futures::Future>>` contains a lifetime `'2`
    |                           has type `&'1 ruma::UserId`

...because the filter function signature doesn't explicitly specify the borrow lifetime as OK to be used for a little while longer;

    fn filter<Fut, F>(self, f: F) -> Filter<Self, Fut, F>
    where
        F: FnMut(&Self::Item) -> Fut,
        Fut: Future<Output = bool>,
        Self: Sized,
    {
        assert_stream::<Self::Item, _>(Filter::new(self, f))
    }

I think that altering it to this fixes it, but i'm not sure how that counts with API cleanliness and lifetime soundness;

    fn filter<'a, Fut, F>(self, f: F) -> Filter<Self, Fut, F>
    where
        F: FnMut(&'a Self::Item) -> Fut,
        Fut: Future<Output = bool> + 'a,
        Self::Item: 'a,
        Self: Sized,
    {
        assert_stream::<Self::Item, _>(Filter::new(self, f))
    }

Quickly inserting this seems to pop up a lot of other issues, so i'm only going to report this issue for now here.

ShadowJonathan avatar Jul 16 '21 19:07 ShadowJonathan

related: https://github.com/rust-lang/futures-rs/pull/2046

taiki-e avatar Aug 07 '21 10:08 taiki-e

Just coming by to leave a friendly note:

As a workaround, .filter_map() + .then_some() might work when .filter() doesn't.

For a more concrete example, see line 19 of this demo.

rami3l avatar Dec 10 '22 15:12 rami3l