What's the difference of lifetime inference between async fn and async closure?

元气小坏坏 提交于 2021-01-28 21:46:08

问题


Look at this code:

#![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);
    });
}

Playground

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`

回答1:


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

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 + '...

Where '2 is the lifetime of the closure.

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

When you call is_a like this:

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

is_a(closure);

You get:

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`

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

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

For:

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

the compiler reports:

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`

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

Why is it needed lifetime '2?

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

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.

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

But from where is coming lifetime '2?

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.

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

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

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



来源:https://stackoverflow.com/questions/60751127/whats-the-difference-of-lifetime-inference-between-async-fn-and-async-closure

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!