Rustクロージャの有効期間を指定します

Aug 21 2020

これが生涯の問題であることを発見している間、私はエグゼキュータ/リアクターを作成していました。async / Futureとは関係がなく、asyncsugarなしで再現できます。

use std::future::Future;

struct Runtime;

fn start_with_runtime<C, F>(closure: C)
where
    C: for<'a> FnOnce(&'a Runtime) -> F,
    F: Future
{
    let rt = Runtime;
    let _future = closure(&rt);
    // block_on(future); 
}

async fn async_main(_rt: &Runtime) {
    // I can use _rt to do async stuff here
}

fn main() {
    start_with_runtime(|rt| { async_main(rt) });
}

start_with_runtime()将来を実行し、パラメーターとして非同期ランタイム参照を提供したいと思います。

コンパイルされません:

error: lifetime may not live long enough
  --> src/main.rs:17:31
   |
17 |     start_with_runtime(|rt| { async_main(rt) });
   |                         ---   ^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'2`
   |                         | |
   |                         | return type of closure is impl std::future::Future
   |                         has type `&'1 Runtime`

この問題は、錆がクロージャーの寿命をどのように推測するかによると思われます。

https://github.com/rust-lang/rust/issues/58052 :

fn main() {
    let f = |x: &i32| x;
    let i = &3;
    let j = f(i);
}

どちらもコンパイルしません:

error: lifetime may not live long enough
 --> src/main.rs:2:23
  |
2 |     let f = |x: &i32| x;
  |                 -   - ^ returning this value requires that `'1` must outlive `'2`
  |                 |   |
  |                 |   return type of closure is &'2 i32
  |                 let's call the lifetime of this reference `'1`

私のクロージャー署名|&'a Runtime| -> impl Future + 'bは、ライフタイムエラーとして推測されたように見えます。閉鎖のために正しい期待される署名が与えられれば役立つと思いますが、どうすれば正しい署名を提供できstart_with_runtimeますか?

fn start_with_runtime<C>(closure: C)
where
    C: for<'a> FnOnce(&'a Runtime) -> (impl Future + 'a),

impl Traitここでは許可されていないため、機能しません。

fn start_with_runtime<C,F>(closure: C)
where
    C: for<'a> FnOnce(&'a Runtime) -> F,
    F: Future + 'a

'aHRTB式以外では知られていないため、うまく機能しません。

タイプがわかっている場合は機能します。


struct MyType<'a> {
    _rt: &'a Runtime
} 
fn start_with_runtime<C>(closure: C)
where
    C: for<'a> FnOnce(&'a Runtime) -> MyType<'a>,

あなたが生涯を通して考えてきたとき、これはちょっと悲しいですが、言語はこれを表現する方法を提供しません。おそらく、これを機能させるための錆のトリックがありますか?

回答

1 Tanriol Aug 24 2020 at 16:22

これには2つの異なる質問があるようです。必要な関係をRust構文で表現できるか、クロージャ型推論で機能するかどうかです。

最初のものから始めましょう。これはwhere節だけでは表現できないのは正しいです。これを表現するには、ヘルパー特性を追加する必要があります

trait BorrowingFn<'a> {
    type Fut: std::future::Future<Output = Something> + 'a;
    fn call(self, arg: &'a Runtime) -> Self::Fut;
}

それは私たちが書く必要がある限界を可能にします

    C: for<'a> BorrowingFn<'a>,

該当するすべての機能に対して、この特性の包括的な実装を提供します

impl<'a, Fu: 'a, F> BorrowingFn<'a> for F
where
    F: FnOnce(&'a Runtime) -> Fu,
    Fu: std::future::Future<Output = ()> + 'a,
{
    type Fut = Fu;
    fn call(self, rt: &'a Runtime) -> Fu {
        self(rt)
    }
}

(遊び場)

さて、それは非同期関数で動作しますが、型推論を必要とするクロージャで動作しますか?残念ながら、答えは「いいえ」です

error: implementation of `BorrowingFn` is not general enough
  --> src/main.rs:33:5
   |
5  | / trait BorrowingFn<'a> {
6  | |     type Fut: std::future::Future<Output = ()> + 'a;
7  | |     fn call(self, arg: &'a Runtime) -> Self::Fut;
8  | | }
   | |_- trait `BorrowingFn` defined here
...
33 |       start_with_runtime(|rt| async_main(rt)); // however, it does not work with closure type inference :-(
   |       ^^^^^^^^^^^^^^^^^^ implementation of `BorrowingFn` is not general enough
   |
   = note: `[closure@src/main.rs:33:24: 33:43]` must implement `BorrowingFn<'0>`, for any lifetime `'0`...
   = note: ...but `[closure@src/main.rs:33:24: 33:43]` actually implements `BorrowingFn<'1>`, for some specific lifetime `'1`

これはrust-lang / rust#70263で追跡されています。コンパイラは、このクロージャがより高いランクの型を必要としていることに気付くほど賢くはありません。楽しみのために-Z chalk、Nightlyでコンパイルしようとしましたが、まだ準備ができていません(内部コンパイラエラー)。

AliceRyhl Aug 23 2020 at 15:08

申し訳ありませんが、これは言語の制限です。コンクリートタイプでのみ寿命を指定できます。回避策の1つは、トレイトオブジェクトタイプを使用することです。

fn start_with_runtime<C, F, T>(closure: C)
where
    C: for<'a> FnOnce(&'a Runtime) -> Pin<Box<dyn Future<Item = T> + Send + 'a>>,
{
    let rt = Runtime;
    let _future = closure(&rt);
    // block_on(future); 
}