从 Rust 中的函数返回一个异步函数

Return an async function from a function in Rust

提问人:Jeswin Kumar 提问时间:4/12/2020 最后编辑:Jeswin Kumar 更新时间:12/8/2022 访问量:11377

问:

第 1 部分:返回异步函数的函数的签名应该是什么?

pub async fn some_async_func(arg: &str) {}

// What should be sig here?
pub fn higher_order_func(action: &str) -> ???
{
    some_async_func
}

第 2 部分:如果基于 action 参数,higher_order_func必须返回 async_func1 或 async_func2,则 sig 应该是什么。

我也有兴趣了解如果有多个解决方案的性能权衡。请注意,我想将函数本身作为 fn 指针或 Fn* 特征返回,而不是调用它的结果。

rust 异步等待

评论


答:

35赞 kreo 4/12/2020 #1

返回函数

返回实际的函数指针需要堆分配和包装器:

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

pub async fn some_async_func(arg: &str) {}

pub fn some_async_func_wrapper<'a>(arg: &'a str)
    -> Pin<Box<dyn Future<Output=()> + 'a>>
{
    Box::pin(some_async_func(arg))
}

pub fn higher_order_func<'a>(action: &str)
    -> fn(&'a str) -> Pin<Box<dyn Future<Output=()> + 'a>>
{
    some_async_func_wrapper
}

为什么选择拳击? 需要有一个具体的返回类型,即函数指针。指向函数还需要有一个具体的返回类型,这对于函数来说是不可能的,因为它返回不透明类型。从理论上讲,可以将返回类型编写为 ,但这需要编译器进行更多的猜测,并且目前不支持。higher_order_funcasyncfn(&'a str) -> impl Future<Output=()> + 'a

如果你同意代替 ,你可以去掉包装器:Fnfn

pub async fn some_async_func(arg: &str) {}

pub fn higher_order_func<'a>(action: &str)
    -> impl Fn(&'a str) -> Pin<Box<dyn Future<Output=()> + 'a>>
{
    |arg: &'a str| {
        Box::pin(some_async_func(arg))
    }
}

要根据值返回不同的函数,您需要将闭包本身装箱,这是另一个堆分配:action

pub async fn some_async_func_one(arg: &str) {}
pub async fn some_async_func_two(arg: &str) {}

pub fn higher_order_func<'a>(action: &str)
    -> Box<dyn Fn(&'a str) -> Pin<Box<dyn Future<Output=()> + 'a>>>
{
    if action.starts_with("one") {
        Box::new(|arg: &'a str| {
            Box::pin(some_async_func_one(arg))
        })
    } else {
        Box::new(|arg: &'a str| {
            Box::pin(some_async_func_two(arg))
        })
    }
}

备选方案:回归未来

为了简化起见,请考虑返回 future 本身而不是函数指针。这几乎是一样的,但要好得多,并且不需要堆分配:

pub async fn some_async_func(arg: &str) {}

pub fn higher_order_func_future<'a>(action: &str, arg: &'a str)
    -> impl Future<Output=()> + 'a
{
    some_async_func(arg)
}

它可能看起来像是,当被调用时,正在被执行 - 但事实并非如此。由于异步函数的工作方式,当您调用时,不会执行任何用户代码。函数调用返回一个 :只有当有人等待返回的未来时,才会执行实际的函数体。higher_order_func_futuresome_async_funcsome_async_funcFuture

您可以使用新函数的方式与上一个函数几乎相同:

// With higher order function returning function pointer
async fn my_function() {
    let action = "one";
    let arg = "hello";
    higher_order_func(action)(arg).await;
}

// With higher order function returning future
async fn my_function() {
    let action = "one";
    let arg = "hello";
    higher_order_func_future(action, arg).await;
}

再次注意,在这两种情况下,实际的身体只有在等待未来时才会被处决。some_async_func

如果您希望能够根据值调用不同的异步函数,则需要再次装箱:action

pub async fn some_async_func_one(arg: &str) {}
pub async fn some_async_func_two(arg: &str) {}

pub fn higher_order_func_future<'a>(action: &str, arg: &'a str)
    -> Pin<Box<dyn Future<Output=()> + 'a>>
{
    if action.starts_with("one") {
        Box::pin(some_async_func_one(arg))
    } else {
        Box::pin(some_async_func_two(arg))
    }
}

不过,这只是一个堆分配,所以我强烈建议返回一个未来。我能想象到的唯一情况是,以前的解决方案更好,当您想将盒装封盖保存在某个地方并多次使用它时。在这种情况下,过度分配只发生一次,并且通过仅根据一次(在进行关闭时)调度调用来节省一些 CPU 时间。action

评论

0赞 Jeswin Kumar 4/12/2020
答案调用函数并返回结果,而不是返回函数本身。我想返回一个函数指针(函数项也可以)。
1赞 kreo 4/12/2020
编辑了答案。最有可能的是,您实际上并不需要函数指针;如果您确定这样做,请分享您的用例详细信息。
1赞 Jeswin Kumar 4/12/2020
感谢您的精彩回答。我试图从另一种语言移植一些模式,这涉及向后发送指针。我可以修改它们以使用 Future,但我也想了解如何完成它。您的帖子内容丰富,在这两个方面都有很大帮助。
0赞 Simon Buchan 6/26/2020
当调用时间很重要时,或者当调用高阶函数创建一些本地状态时,您会遇到想要返回异步函数的情况:例如。当高阶函数在一段时间内缓存异步函数的结果时(由函数的结果决定,因此您不能只执行带有超时的标准选择)。
4赞 Sam Thomas 11/29/2021
这仍然是在 stable rust(2021) 中做到这一点的最佳方法吗?
2赞 Chayim Friedman 7/4/2022 #2

理想情况下,您想要的是一个嵌套的 impl 特征:.但不支持嵌套的 impl trait。但是,您可以使用特征来模拟它。-> impl Fn(&str) -> impl Future<Output = ()>

这个想法是定义一个特征,该特征将抽象为“函数返回未来”的概念。如果我们的函数以 ,例如,它可能如下所示:u32

trait AsyncFn: Fn(u32) -> Self::Future {
    type Future: Future<Output = ()>;
}
impl<F, Fut> AsyncFn for F
where
    F: Fn(u32) -> Fut,
    Fut: Future<Output = ()>,
{
    type Future = Fut;
}

然后我们会采取.试图天真地将其应用于是行不通的:impl AsyncFn&str

error[E0308]: mismatched types
  --> src/lib.rs:16:27
   |
16 | fn higher_order_func() -> impl AsyncFn {
   |                           ^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected associated type `<for<'_> fn(&str) -> impl Future<Output = ()> {some_async_func} as FnOnce<(&str,)>>::Output`
              found associated type `<for<'_> fn(&str) -> impl Future<Output = ()> {some_async_func} as FnOnce<(&str,)>>::Output`

这个错误可能看起来很奇怪,但它源于这样一个事实,即返回一个受其所有参数的生存期约束的未来,即对于签名,未来不是而是。有一种方法可以在我们的特征中捕获这种关系,我们只需要在参数上通用并使用 HRTBasync fnasync fn foo<'a>(arg: &'a str)impl Future<Output = ()>impl Future<Output = ()> + 'a

trait AsyncFn<Arg>: Fn(Arg) -> Self::Future {
    type Future: Future<Output = ()>;
}
impl<Arg, F, Fut> AsyncFn<Arg> for F
where
    F: Fn(Arg) -> Fut,
    Fut: Future<Output = ()>,
{
    type Future = Fut;
}

然后我们将类型指定为:

fn higher_order_func() -> impl for<'a> AsyncFn<&'a str> {
    some_async_func
}

评论

0赞 Venryx 12/22/2022
效果很好!(无论如何,对于我的用例)我在这里整理了一个 Gist,其中包含一些特征的扩展,用于输出类型和可变数量的参数。AsyncFn
0赞 Mallux 12/8/2022 #3

除了公认的答案之外,根据您的用例,还可以通过使用简单的宏来就地扩展包装代码来“伪造”高阶函数并避免任何堆分配:

pub async fn some_async_func(arg: &str) {}

macro_rules! higher_order_func {
    ($action: expr) => {
        some_async_func
    };
}

fn main() {
    let future = higher_order_func!("action")("arg");
}