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

r1wp621o  于 2022-12-19  发布在  其他
关注(0)|答案(3)|浏览(152)

第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* trait,而不是调用它的结果。

bpzcxfmw

bpzcxfmw1#

返回函数

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

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_func需要有一个具体的返回类型,即函数指针。被指向的函数也需要有一个具体的返回类型,这对于async函数是不可能的,因为它返回不透明类型。理论上,可以将返回类型写为fn(&'a str) -> impl Future<Output=()> + 'a,但这需要编译器进行更多的猜测,目前不支持。
如果您可以使用Fn代替fn,那么可以去掉 Package 器:

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_future被调用时,看起来好像some_async_func正在被执行--但事实并非如此。由于异步函数的工作方式,当你调用some_async_func时,没有用户代码正在被执行。函数调用返回Future:实际的函数体将在某人等待返回的future时被执行。
新函数的使用方法与上一个函数几乎相同:

// 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))
    }
}

尽管如此,这只是一次堆分配,所以我强烈建议返回一个future。我能想象到的唯一一种情况是,当你想把装箱的闭包保存在某个地方并多次使用它时,前面的解决方案更好。在这种情况下,过度分配只发生一次,当你进行闭包时,你可以通过只调度一次基于action的调用来节省一些CPU时间。

jdzmm42g

jdzmm42g2#

理想情况下,您需要的是一个嵌套的impl trait:-> impl Fn(&str) -> impl Future<Output = ()>。但是不支持嵌套的impl trait。但是,您可以使用trait模拟它。
我们的想法是定义一个trait来抽象“返回未来的函数”的概念,例如,如果我们的函数接受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,试着把它简单地应用到&strdoesn't work上:

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`

这个错误可能看起来很奇怪,但是它是由async fn返回一个未来值的事实引起的,这个未来值是由它的所有参数的生存期所限定的,也就是说,对于一个签名async fn foo<'a>(arg: &'a str),未来值不是impl Future<Output = ()>而是impl Future<Output = ()> + 'a。有一种方法可以在我们的trait中捕捉这种关系,我们只需要使它通用于参数并使用HRTB

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
}
n53p2ov0

n53p2ov03#

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

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");
}

相关问题