Specificare la durata delle chiusure Rust
Stavo facendo l'esecutore / reattore mentre ho scoperto che questo era un problema a vita. Non è correlato ad async / Future e può essere riprodotto senza zucchero asincrono.
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) });
}
Vorrei start_with_runtime()
eseguire il futuro e fornire il riferimento runtime asincrono come parametro.
Non compila:
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`
Penso che questo problema sembri essere dovuto al modo in cui la ruggine deduce la durata delle chiusure:
https://github.com/rust-lang/rust/issues/58052 :
fn main() {
let f = |x: &i32| x;
let i = &3;
let j = f(i);
}
Non compila neanche:
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`
Sembra che la mia firma di chiusura sia stata dedotta come |&'a Runtime| -> impl Future + 'b
e quindi l'errore di durata. Ritengo che dare la firma corretta prevista per la chiusura sarebbe d'aiuto, ma come faccio a fornire la firma corretta in start_with_runtime
?
fn start_with_runtime<C>(closure: C)
where
C: for<'a> FnOnce(&'a Runtime) -> (impl Future + 'a),
Non funziona perché impl Trait
non è consentito qui.
fn start_with_runtime<C,F>(closure: C)
where
C: for<'a> FnOnce(&'a Runtime) -> F,
F: Future + 'a
Non funziona anche perché 'a
non è noto al di fuori dell'espressione HRTB.
Funziona se conosco il tipo:
struct MyType<'a> {
_rt: &'a Runtime
}
fn start_with_runtime<C>(closure: C)
where
C: for<'a> FnOnce(&'a Runtime) -> MyType<'a>,
Questo è un po 'triste quando hai riflettuto su tutte le vite, ma il linguaggio non fornisce un modo per esprimerlo. Forse c'è un trucco nella ruggine per far funzionare questo?
Risposte
Sembra che ci siano due diverse domande in questa: la relazione richiesta può essere espressa nella sintassi Rust e funzionerà con l'inferenza del tipo di chiusura oppure no.
Cominciamo con il primo. Hai ragione che questo non può essere espresso con giuste where
clausole. Per esprimere questo è necessario aggiungere un tratto aiutante
trait BorrowingFn<'a> {
type Fut: std::future::Future<Output = Something> + 'a;
fn call(self, arg: &'a Runtime) -> Self::Fut;
}
che consente di scrivere il limite come
C: for<'a> BorrowingFn<'a>,
e fornire un'implementazione generale di questa caratteristica per tutte le funzioni applicabili
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)
}
}
( parco giochi )
Ok, quindi funziona con una funzione asincrona, ma funziona con una chiusura che necessita di inferenza di tipo? Sfortunatamente la risposta è no"
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`
Questo è stato rintracciato in rust-lang / rust # 70263 . Il compilatore non è ancora abbastanza intelligente da notare che questa chiusura necessita di un tipo di rango superiore. Per divertimento ho provato a compilare -Z chalk
su Nightly, ma non è ancora pronto per questo (errore del compilatore interno).
Spiacenti, questa è una limitazione nella lingua. È possibile specificare la durata solo sui tipi di calcestruzzo. Una soluzione alternativa consiste nell'usare il tipo di oggetto tratto.
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);
}