提问人:Jeswin Kumar 提问时间:4/12/2020 最后编辑:Jeswin Kumar 更新时间:12/8/2022 访问量:11377
从 Rust 中的函数返回一个异步函数
Return an async function from a function in Rust
问:
第 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* 特征返回,而不是调用它的结果。
答:
返回函数
返回实际的函数指针需要堆分配和包装器:
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
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
您可以使用新函数的方式与上一个函数几乎相同:
// 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
评论
理想情况下,您想要的是一个嵌套的 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`
这个错误可能看起来很奇怪,但它源于这样一个事实,即返回一个受其所有参数的生存期约束的未来,即对于签名,未来不是而是。有一种方法可以在我们的特征中捕获这种关系,我们只需要在参数上通用并使用 HRTB:async fn
async 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
}
评论
除了公认的答案之外,根据您的用例,还可以通过使用简单的宏来就地扩展包装代码来“伪造”高阶函数并避免任何堆分配:
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");
}
评论