如何创建一个泛型函数来包装异步闭包?

How to create a generic function to wrap an async closure?

提问人:rv.kvetch 提问时间:3/25/2022 最后编辑:rv.kvetch 更新时间:3/26/2022 访问量:930

问:

我正在尝试创建一个通用函数,该函数具有包装异步闭包的令人印象深刻的目标。因此,出于所有意图和目的,它会将闭包视为一个黑匣子,并简单地在之前运行一些设置逻辑,然后运行一些逻辑,具体取决于传入的闭包的返回值。

这是我到目前为止整理的 MRE 示例代码。我也在 Rust playground 中测试了它。

use std::future::Future;
use std::pin::Pin;

#[derive(Default)]
struct MyEvent {}

#[derive(Default)]
struct MyContext {
    pub fn_name: String,
}

pub struct HelpAnError {}

#[tokio::main]
async fn main() {
    let my_age: u8 = 29;

    let doing_work_hard = move |event: MyEvent, ctx: MyContext| async move {
        println!("I'm working hard, i promise!");
        println!("Ma i'm already {} yrs old!", my_age);
        Ok::<_, HelpAnError>(())
    };

    let doing_chores_hard = lets_wrap_da_closure(doing_work_hard);

    // does all the chores that Ma assigned to me
    let _ = doing_chores_hard(Default::default(), Default::default()).await;
}

fn lets_wrap_da_closure<
    C: Fn(MyEvent, MyContext) -> F,
    F: Future<Output = Result<(), HelpAnError>>,
>(
    do_work_hard: C,
) -> fn(MyEvent, MyContext) -> Pin<Box<dyn Future<Output = Result<(), HelpAnError>>>> {
    move |event: MyEvent, ctx: MyContext| {
        Box::pin(async move {
            println!("I'm doin my chores Ma, I promise!");
            // **help** - if I uncomment this it fails!
            // do_work_hard(event, ctx).await;
            println!("Yay! The chores are now complit.");
            Ok(())
        })
    }
}

我将不胜感激任何关于如何让它与 rust 编译器一起使用的帮助或指示,这对我来说似乎不太友好。我觉得我已经挣扎了几个小时,但不够聪明或不够熟练,不知道如何满足编译器规则。

我还检查了从 Rust 中的函数返回异步函数,并获得了相应使用的提示。但是,我在为我的特定用例实现它时遇到了麻烦,即我想创建一个泛型函数来包装一个返回特定值的可调用对象。谢谢,如果我需要澄清任何事情,请告诉我。Box::pinFuture


更新:感谢@Jakub的帮助。以下是我修改后的(工作)代码:

use std::future::Future;
use std::pin::Pin;

#[derive(Default)]
struct MyEvent {}

#[derive(Default)]
struct MyContext {
    pub fn_name: String,
}

#[derive(Debug)]
pub struct HelpAnError {}

#[tokio::main]
async fn main() {
    let my_age: u8 = 29;

    let doing_work_hard = move |event: MyEvent, ctx: MyContext| async move {
        println!("I'm working hard, i promise!");
        println!("Ma i'm already {} yrs old!", my_age);
        Ok::<_, HelpAnError>(())
    };

    let doing_chores_hard = lets_wrap_da_closure(doing_work_hard);

    // does all the chores that Ma assigned to me
    let _ = doing_chores_hard(Default::default(), Default::default()).await;
}

fn lets_wrap_da_closure<
    'a,
    C: Fn(A1, A2) -> F,
    A1,
    A2,
    E: std::fmt::Debug,
    F: Future<Output = Result<(), E>> + 'a,
>(
    do_work_hard: C,
) -> impl Fn(A1, A2) -> Pin<Box<dyn Future<Output = Result<(), E>> + 'a>> {
    move |event: A1, ctx: A2| {
        let fut = do_work_hard(event, ctx);
        Box::pin(async move {
            println!("I'm doin my chores Ma, I promise!");
            // it WORKS! wow, amazing ~
            match fut.await {
                Ok(_) => println!("All systems are a GO!"),
                Err(e) => println!("I ran into issue doing my chores: {:?}", e),
            };
            println!("Yay! The chores are now complit.");
            Ok(())
        })
    }
}

旁注:将它包装成一个通用的,或者只是重用 ,但在这一点上,我很高兴能够让它工作:-)dyn Future<Output = Result<(), HelpAnError>>F

异步 rust async-await 闭包 rust-tokio

评论


答:

1赞 Jakub Dóka 3/26/2022 #1
use std::future::Future;
use std::pin::Pin;

#[derive(Default)]
struct MyEvent {}

#[derive(Default)]
struct MyContext {
    pub fn_name: String,
}

pub struct HelpAnError {}

#[tokio::main]
async fn main() {
    let my_age: u8 = 29;

    let doing_work_hard = move |event: MyEvent, ctx: MyContext| async move {
        println!("I'm working hard, i promise!");
        println!("Ma i'm already {} yrs old!", my_age);
        Ok::<_, HelpAnError>(())
    };

    let doing_chores_hard = lets_wrap_da_closure(doing_work_hard);

    // does all the chores that Ma assigned to me
    let _ = doing_chores_hard(Default::default(), Default::default()).await;
}

fn lets_wrap_da_closure<
    C: Fn(MyEvent, MyContext) -> F + 'static,
    F: Future<Output = Result<(), HelpAnError>> + 'static,
>(
    do_work_hard: C,
) -> impl Fn(MyEvent, MyContext) -> Pin<Box<dyn Future<Output = Result<(), HelpAnError>>>> {
    move |event: MyEvent, ctx: MyContext| {
        // this is the only way i can satisfy the compiler, do not worry,
        // future will get executed only upon await and this call is just 
        // creating the future we are passing
        let fut = do_work_hard(event, ctx);
        Box::pin(async move {
            println!("I'm doin my chores Ma, I promise!");
            // **help** - if I uncomment this it fails!
            drop(fut.await);
            println!("Yay! The chores are now complit.");
            Ok(())
        })
    }
}

评论

0赞 rv.kvetch 3/26/2022
太棒了!我将不得不阅读更多关于这个特定用例的信息,假设它在 Rust 书或其他地方提到过,但在这一点上,我真的很高兴我提出的建议实际上可以以某种方式实现。道具给你,我的朋友!
0赞 rv.kvetch 3/26/2022
只是一个小说明,但我发现,除了显式语法之外,您还可以让它喜欢并忽略显式 .我认为你不能在这里使用速记的唯一原因是因为我没有为我的类型正确实现 Error。我不完全确定这种方法之间的区别,但如果我理解正确,它应该或多或少具有相同的结果。droplet _ = fut.awaitResult?HelpAnErrordrop
0赞 Jakub Dóka 3/26/2022
这只是我的偏好,它主要证明当您想制作 match 语句的速记分支但不从分支返回任何值时很有用。