Specificare la durata delle chiusure Rust

Aug 21 2020

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 + 'be 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 Traitnon è 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é 'anon è 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

1 Tanriol Aug 24 2020 at 16:22

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 whereclausole. 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 chalksu Nightly, ma non è ancora pronto per questo (errore del compilatore interno).

AliceRyhl Aug 23 2020 at 15:08

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