异步fn和异步闭包之间的生存期推断有什么区别? [英] What's the difference of lifetime inference between async fn and async closure?

查看:52
本文介绍了异步fn和异步闭包之间的生存期推断有什么区别?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

看下面的代码:

#![feature(async_closure)]

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

trait A<'a> {
    fn call(&'a self, data: &'a i32) -> Pin<Box<dyn 'a + Future<Output=()>>>;
}

impl <'a, F, Fut> A<'a> for F
where Fut: 'a + Future<Output=()>,
      F: Fn(&'a i32) -> Fut
{
    fn call(&'a self, data: &'a i32) -> Pin<Box<dyn 'a + Future<Output=()>>> {
        Box::pin(self(data))
    }
}

async fn sample(_data: &i32) {

}

fn is_a(_: impl for<'a> A<'a>) {

}

fn main() {
    is_a(sample);
    is_a(async move |data: &i32| {
        println!("data: {}", data);
    });
}

游乐场

为什么 is_a(sample)有效,但是下一行无法编译?异步fn和异步闭包之间的生存期推断有什么区别?

Why does is_a(sample) works but the next line fails to be compiled? What's the difference of lifetime inference between async fn and async closure?

关闭版本失败,并出现以下错误:

The closure version fails with the following error:

error: implementation of `A` is not general enough
  --> src/main.rs:29:5
   |
6  | / trait A<'a> {
7  | |     fn call(&'a self, data: &'a i32) -> Pin<Box<dyn 'a + Future<Output=()>>>;
8  | | }
   | |_- trait `A` defined here
...
29 |       is_a(async move |data: &i32| {
   |       ^^^^ implementation of `A` is not general enough
   |
   = note: `A<'1>` would have to be implemented for the type `[closure@src/main.rs:29:10: 31:6]`, for any lifetime `'1`...
   = note: ...but `A<'_>` is actually implemented for the type `[closure@src/main.rs:29:10: 31:6]`, for some specific lifetime `'2`

推荐答案

async || 闭包的返回类型是编译器生成的匿名类型.

The return type of an async || closure is an anonymous type generated by the compiler.

这种类型实现了 Future ,并捕获了另外一个生存期与 async || 闭包的范围有关.

Such type implements a Future and it captures an additional lifetime related to the scope of the async || closure.

fn main() {

    let closure = async move |data: &i32| {   --+ '2 start
        println!("data: {}", data);             |
    };                                          |
                                                |
    is_a(closure);                              |
                                                v 
}

异步块返回带有签名的类型,例如:

The async block returns a type with signature like:

impl Future<Output = SomeType> + '2 + '...

其中'2 是闭包的生存期.

请注意,使用异步函数而不是闭包时,没有额外的生存期要求.

Note that there isn't this additional lifetime requirement when using an async function instead of a closure.

当您像这样呼叫 is_a 时:

let closure = async move |data: &i32| {
    println!("data: {}", data);
};

is_a(closure);

您得到:

error: implementation of `A` is not general enough
...
= note: `A<'1>` would have to be implemented for the type `[closure@src/main.rs:43:19: 45:6]`, for any lifetime `'1`...
= note: ...but `A<'_>` is actually implemented for the type `[closure@src/main.rs:43:19: 45:6]`, for some specific lifetime `'2`

因为 closure 参数是为特定生存期'2 实现的类型,但需要任何生存期:

because the closure argument is a type implemented for a specific lifetime '2 but any lifetime is required:

fn is_a(_: impl for<'a> A<'a>) {}

请注意,您注意到的错误确实隐藏了另一个源自捕获的'2 生命周期的生命周期违规.

Note that the error you notice indeed hides another lifetime violation that originate from the captured '2 lifetime.

针对:

let closure = async move |data: &i32| {
    println!("data: {}", data);
};

编译器报告:

error: lifetime may not live long enough
  --> src/main.rs:43:19
   |
43 |     let closure = async move |data: &i32| {
   |                   ^^^^^^^^^^^^^^^^^^-^^^-
   |                   |                 |   |
   |                   |                 |   return type of closure is impl std::future::Future
   |                   |                 let's call the lifetime of this reference `'1`
   |                   returning this value requires that `'1` must outlive `'2`

让我得出结论,不可能在 async || 闭包内使用参数引用.

that let me conclude that it is not possible to use argument references inside an async || closure.

生命周期概念是关于内存安全的,归结为保证参考指向内存插槽指向有效值.

The lifetime concept is about memory safety and it boils down to garantee that a reference pointing to a memory slot is pointing to a valid value.

fn my_function() {

    let value = 1                           --+ '1 start           
                                              |
    let closure = async move |data: &i32| {   |       --+ '2 start
        println!("data: {}", data);           |         |
    };                                        |         |
                                              |         |
    tokio::spawn(closure(&value))             |         |
                                             -+ '1 end  |
}                                                       v continue until   
                                                          the future complete

考虑上面的示例:value是在堆栈上分配的内存插槽,它将一直有效到 my_function 返回并且堆栈展开.

Consider the above example: value is a memory slot allocated on the stack and it will be valid until my_function returns and the stack unwind.

my_function 返回时,生存期'1 考虑到 value 的有效范围引用& value 无效.

The lifetime '1 take account of the scope of validity of value, when my_function returns the reference &value is not more valid.

但是,终生'2 从何而来?

这是因为 closure(& value)返回一个实现了 Future 的实体,该实体将存在于运行时执行器中,在这种情况下,是东京执行器,直到计算结束.

It is because closure(&value) returns an entity that implements a Future that will live into the runtime executor, in this case the tokio executor, until the computation will end.

'2 的生​​存期将考虑 Future 的有效性范围.

The '2 lifetime will take this scope of validity of the Future into account.

为了使'2 生命周期成为必要,请考虑以下情形:

For making a reason of '2 lifetime necessity consider the following scenario:

fn run_asyn_closure() {
    let data: i32 = 1;

    let closure = async move |data: &i32| {
        println!("starting task with data {}", data);

        // yield the computation for 3 seconds, awaiting for completion of long_running_task
        long_running_task().await;

        // data points to a memory slot on the stack that meantime is rewritten
        // because run_asyn_closure returned 3 seconds ago
        println!("using again data: {}", data); // BANG!! data is not more valid
    };

    tokio::spawn(closure(&data));
}

请注意,实际上 tokio :: spawn 需要& data 引用具有'static 生存期,但这与理解这个主题无关.

Note that in reality tokio::spawn needs that &data reference has 'static lifetime, but this is irrelevant to understand this theme.

这篇关于异步fn和异步闭包之间的生存期推断有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆