异步闭包在等待点上保持引用

huangapple go评论65阅读模式
英文:

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

playground

出现错误:

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

playground

但考虑到它们还不稳定,我想知道是否有其他方法可以使这个工作。

英文:

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

playground

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

playground

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

huangapple
  • 本文由 发表于 2023年7月6日 11:48:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76625378.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定