簡體   English   中英

異步 fn 和異步閉包之間的生命周期推斷有什么區別?

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

看看這段代碼:

#![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 和異步閉包之間的生命周期推斷有什么區別?

關閉版本失敗並出現以下錯誤:

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 ||的返回類型閉包是由編譯器生成的匿名類型。

這種類型實現了一個Future並且它捕獲了一個與async ||范圍相關的額外生命周期關閉。

fn main() {

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

異步塊返回帶有簽名的類型,如:

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

其中'2是閉包的生命周期。

請注意,當使用異步函數而不是閉包時,沒有這個額外的生命周期要求。

當你像這樣調用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實現的類型,但需要任何生命周期:

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

請注意,您注意到的錯誤確實隱藏了源自捕獲的'2生命周期的另一個生命周期違規。

為了:

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 ||使用參數引用關閉。

為什么需要生命周期'2?

生命周期概念是關於內存安全的,它歸結為保證指向內存插槽的引用指向有效值。

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返回並且堆棧展開之前一直有效。

生命周期'1考慮到value的有效性范圍,當my_function返回引用時&value不再有效。

但是,life '2是從哪里來'2

這是因為closure(&value)返回一個實現Future的實體,該實體將存在於運行時執行程序中,在本例中為 tokio 執行程序,直到計算結束。

'2生命周期將考慮Future這種有效性范圍。

出於'2生命周期必要性'2的原因,請考慮以下情況:

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生命周期”,但這與理解此主題無關​​。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM