Rustクロージャの有効期間を指定します
これが生涯の問題であることを発見している間、私はエグゼキュータ/リアクターを作成していました。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
'a
HRTB式以外では知られていないため、うまく機能しません。
タイプがわかっている場合は機能します。
struct MyType<'a> {
_rt: &'a Runtime
}
fn start_with_runtime<C>(closure: C)
where
C: for<'a> FnOnce(&'a Runtime) -> MyType<'a>,
あなたが生涯を通して考えてきたとき、これはちょっと悲しいですが、言語はこれを表現する方法を提供しません。おそらく、これを機能させるための錆のトリックがありますか?
回答
これには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でコンパイルしようとしましたが、まだ準備ができていません(内部コンパイラエラー)。
申し訳ありませんが、これは言語の制限です。コンクリートタイプでのみ寿命を指定できます。回避策の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);
}