0

I'm working on this function:

use std::{sync::Arc, collections::HashSet};
use futures::{stream::BoxStream, StreamExt,TryStreamExt};
use brightness::{Brightness, BrightnessDevice};

        fn get_devices<'a>(device_names: Arc<HashSet<String>>) -> BoxStream<'a, Result<BrightnessDevice, brightness::Error>> {
        if device_names.is_empty() {
            brightness::brightness_devices().boxed()
        } else {
            brightness::brightness_devices()
                .try_filter(move |device| {
                    let device_names_clone = device_names.clone();
                    async move {
                        let devname: Result<String, brightness::Error> = device.device_name().await;
                        devname.is_ok_and(|devname| device_names_clone.contains(&devname)) // bool
                }})
                .boxed()
        }
    }

Basically I'm getting a stream of devices that have a brightness value (like computer screens) and if the user specifies a couple of names, I want to filter that stream to only pass those devices that have that name. This name is requested from the device async. I know that that set of names needs to keep existing for the whole duration of the stream, so I wrapped it in an Arc, which seems to fix the first issue I had. It's my first time working with async Rust though so I might be missing something.

I keep getting this error with the lifetimes:

error: lifetime may not live long enough
  --> src\funcs\mod.rs:55:21
   |
53 |                   .try_filter(move |device| {
   |                                     ------- return type of closure `[async block@src\funcs\mod.rs:55:21: 58:18]` contains a lifetime `'2`
   |                                     |
   |                                     has type `&'1 brightness::BrightnessDevice`
54 |                       let device_names_clone = device_names.clone();
55 | /                     async move {
56 | |                         let devname: Result<String, brightness::Error> = device.device_name().await;
57 | |                         devname.is_ok_and(|devname| device_names_clone.contains(&devname)) // bool
58 | |                 }})
   | |_________________^ returning this value requires that `'1` must outlive `'2`

For the brightness functions see this cargo. Or you can assume these functions exist:

brightness::r#async
pub fn brightness_devices() -> impl Stream<Item = Result<BrightnessDevice, Error>>
brightness::r#async::BrightnessDevice
fn device_name<'life0, 'async_trait>(&'life0 self) -> ::core::pin::Pin<Box<dyn ::core::future::Future<Output = Result<String, Error>> + ::core::marker::Send + 'async_trait>>
where
    'life0: 'async_trait,
    Self: 'async_trait
Typhaon
  • 828
  • 8
  • 27

2 Answers2

1

Ok so the code went through some other changes, but I found a solution: filter_map. Why this works though, I have no clue.

pub enum DeviceSelector {
    All,
    ByName(Arc<HashSet<String>>),
}

fn select_devices(
    selector: &DeviceSelector,
) -> BoxStream<Result<BrightnessDevice, brightness::Error>> {
    async fn filter_by_name(
        device_names: Arc<HashSet<String>>,
        device: Result<BrightnessDevice, brightness::Error>,
    ) -> Option<Result<BrightnessDevice, brightness::Error>> {
        if let Ok(device) = device {
            if device
                .device_name()
                .await
                .is_ok_and(|name| device_names.contains(&name))
            {
                return Some(Ok(device));
            }
        }
        None
    }

    match selector {
        DeviceSelector::All => brightness::brightness_devices().boxed(),
        DeviceSelector::ByName(device_names) => brightness::brightness_devices()
            .filter_map(move |dev| {
                let device_names = device_names.clone();
                async move { filter_by_name(device_names, dev).await }
            })
            .boxed(),
    }
}
Typhaon
  • 828
  • 8
  • 27
0

The problem is that you are trying to return a value which has been moved to another scope. When async block scopes ends the value is dropped and does not live longer enough to be returned. That is why gives a lifetime error.

Maybe to solve this issue you should make async the closure inside try_filter



use std::{sync::Arc, collections::HashSet};
use futures::{stream::BoxStream, StreamExt,TryStreamExt};
use brightness::{Brightness, BrightnessDevice};

        fn get_devices<'a>(device_names: Arc<HashSet<String>>) -> BoxStream<'a, Result<BrightnessDevice, brightness::Error>> {
        if device_names.is_empty() {
            brightness::brightness_devices().boxed()
        } else {
            brightness::brightness_devices()
                .try_filter(async move |device| {
                    let devname: Result<String, brightness::Error> = device.device_name().await;
                    devname.is_ok_and(|devname| device_names.contains(&devname)) // bool
                })
                .boxed()
        }
    }


I did not test this code, Tell me if you find other issue.

PXP9
  • 358
  • 1
  • 8
  • Sadly your solution doesn't work. async closures like yours is only available on nightly channels, the earlier issue persist, and I get an ownership error on `device_names`. Thanks for the suggestion though – Typhaon Jul 21 '23 at 11:16
  • What ownership error do you have ? @Typhaon – PXP9 Jul 21 '23 at 11:55
  • I have the pastebin here: https://pastebin.com/UbJD15fK @PXP9 – Typhaon Jul 21 '23 at 12:28