英文:
Async closure holding reference over await point
问题
有没有一种方法可以创建一个异步闭包,该闭包在 await 点上持有一个引用?
示例:
use std::time::Duration;
use tokio::time::sleep;
fn main() {
let closure = |v: &u64| async move {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
};
}
出现错误:
error: lifetime may not live long enough
--> src/main.rs:5:29
|
5 | let closure = |v: &u64| async move {
| _______________________-___-^
| | | |
| | | 返回闭包的类型包含生命周期 `'2'`
| | 让我们称此引用的生命周期为 `'1'`
6 | | sleep(Duration::from_secs(1)).await;
7 | | println!("{}", v);
8 | | };
| |_____^ 返回此值要求 `'1'` 必须超过 `'2'`
我知道异步闭包可以解决这个问题。这个版本是可行的:
#![feature(async_closure)]
use std::time::Duration;
use tokio::time::sleep;
fn main() {
let closure = async move |v: &u64| {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
};
}
但考虑到它们还不稳定,我想知道是否有其他方法可以使这个工作。
英文:
Is there a workaround for creating an async closure that holds a reference over an await point?
Example:
use std::time::Duration;
use tokio::time::sleep;
fn main() {
let closure = |v: &u64| async move {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
};
}
Fails with:
error: lifetime may not live long enough
--> src/main.rs:5:29
|
5 | let closure = |v: &u64| async move {
| _______________________-___-_^
| | | |
| | | return type of closure `[async block@src/main.rs:5:29: 8:6]` contains a lifetime `'2`
| | let's call the lifetime of this reference `'1`
6 | | sleep(Duration::from_secs(1)).await;
7 | | println!("{}", v);
8 | | };
| |_____^ returning this value requires that `'1` must outlive `'2`
I'm aware that async closures could help. This works:
#![feature(async_closure)]
use std::time::Duration;
use tokio::time::sleep;
fn main() {
let closure = async move |v: &u64| {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
};
}
but given that they're not stable yet, I was wondering if there is any other workaround to make this work.
答案1
得分: 1
问题在于没有办法指示闭包的返回值包括引用。通常,您可以使用 impl
语法来实现这一点,但在闭包中不允许这样做。
所以,您可以将其改为异步函数,这样会自动处理。
fn main() {
let closure = |v| c(v);
}
async fn c(v: &u64) {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
}
如果需要捕获任何东西,可以将其添加到函数参数中。
英文:
The issue here is that there's no way to indicate that the closure's return includes the reference. Normally you can do this with impl
syntax, but that's not allowed on closures.
// not allowed
|v: &'a u64| -> impl Future + 'a
So instead, you can make it an async function, which does this automatically.
fn main() {
let closure = |v| c(v);
}
async fn c(v: &u64) {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
}
If you need to capture anything, you can add it to the function arguments.
答案2
得分: 1
问题在于编译器推断闭包的生命周期为 &'
some_lifetime u64,而不是像函数一样推断为
&'any_lifetime u64`。
通常情况下,当遇到这个问题时,我们可以将闭包传递给一个接受它并返回它的函数,但限制闭包为 for<'a> Fn(&'a u64)
(或只是 Fn(&u64)
),这有助于编译器推断正确的生命周期。但在这种情况下,我们无法这样做,因为这样的函数将禁止返回的 future 从参数中借用,就像我在 https://stackoverflow.com/questions/70591386/calling-a-generic-async-function-with-a-mutably-borrowed-argument/ 中解释的那样。
如果可以将闭包更改为函数,这是最简单的解决方案。
否则,如果闭包捕获了变量,您可以将返回的 future 放入 Box 中,然后使用上述函数:
use std::future::Future;
use std::pin::Pin;
fn force_hrtb<F: Fn(&u64) -> Pin<Box<dyn Future<Output = ()> + '_>>>(f: F) -> F {
f
}
let closure = force_hrtb(|v: &u64| {
Box::pin(async move {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
})
});
如果箱化和动态分派的成本不可接受,但您可以使用夜间版本,可以使用不稳定特性closure_lifetime_binder
来强制编译器将 &u64
视为 &'any_lifetime u64
。不幸的是,由于 closure_lifetime_binder
要求明确编写返回类型,我们还需要使用另一个不稳定特性type_alias_impl_trait
来实现这一点:
#![feature(closure_lifetime_binder, type_alias_impl_trait)]
use std::future::Future;
type ClosureRet<'a> = impl Future<Output = ()> + 'a;
let closure = for<'a> |v: &'a u64| -> ClosureRet<'a> {
async move {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
}
};
英文:
The problem is that the compiler infers the closure to accept &'some_lifetime u64
instead of &'any_lifetime u64
as it would do for functions.
Usually, when this is a problem we can pass the closure into a function that takes it and returns it but constrain the closure to be for<'a> Fn(&'a u64)
(or just Fn(&u64)
), and this helps the compiler infer the right lifetime. But here we cannot do that, because such function will disallow the returned future to borrow from the parameter, as I explained in https://stackoverflow.com/questions/70591386/calling-a-generic-async-function-with-a-mutably-borrowed-argument/.
If you can change the closure to a function, this is the simplest solution.
Otherwise, if the closure captures, you can box the returned future and then use the aforementioned function:
use std::future::Future;
use std::pin::Pin;
fn force_hrtb<F: Fn(&u64) -> Pin<Box<dyn Future<Output = ()> + '_>>>(f: F) -> F {
f
}
let closure = force_hrtb(|v: &u64| {
Box::pin(async move {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
})
});
If the cost of boxing and dynamic dispatch is unacceptable, but you can use nightly, you can use the unstable feature closure_lifetime_binder
to force the compiler to treate &u64
as &'any_lifetime u64
. Unfortunately, because closure_lifetime_binder
requires the return type to be written explicitly, we also need to do that and we can do that only with another unstable feature, type_alias_impl_trait
:
#![feature(closure_lifetime_binder, type_alias_impl_trait)]
use std::future::Future;
type ClosureRet<'a> = impl Future<Output = ()> + 'a;
let closure = for<'a> |v: &'a u64| -> ClosureRet<'a> {
async move {
sleep(Duration::from_secs(1)).await;
println!("{}", v);
}
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论