In che modo la syntax si differenzia da una normale durata di vita?

Considera il seguente codice:

trait Trait {} fn foo(_b: Box<Trait>) {} fn bar(_b: Box<for Trait>) {} 

Entrambe le funzioni foo e bar sembrano accettare un Box<Trait> , sebbene foo faccia più concisamente della bar . Qual’è la differenza tra loro?

Inoltre, in quali situazioni avrei bisogno for syntax come quella sopra? So che la libreria standard di Rust lo utilizza internamente (spesso in relazione alle chiusure), ma perché il mio codice potrebbe averne bisogno?

for<> syntax è chiamata trait del tratto più elevato (HRTB), ed è stata introdotta per lo più a causa delle chiusure.

In breve, la differenza tra foo e bar è che in foo() la durata per il riferimento di usize interno è fornita dal chiamante della funzione, mentre in bar() la stessa durata è fornita dalla funzione stessa . E questa distinzione è molto importante per l’implementazione di foo / bar .

Tuttavia, in questo caso particolare, quando Trait non ha metodi che usano il parametro type, questa distinzione è inutile, quindi immaginiamo che Trait assomigli a questo:

 trait Trait { fn do_something(&self, value: T); } 

Ricorda, i parametri di durata sono molto simili ai parametri di tipo generico. Quando si utilizza una funzione generica, si specificano sempre tutti i parametri del tipo, fornendo tipi concreti e il compilatore monomorfizza la funzione. La stessa cosa vale per i parametri di durata: quando si chiama una funzione che ha un parametro di durata, si specifica la durata, anche se implicitamente:

 // imaginary explicit syntax // also assume that there is TraitImpl::new::() -> TraitImpl, // and TraitImpl: Trait 'a: { foo::< 'a>(Box::new(TraitImpl::new::< &'a usize>())); } 

E ora c’è una limitazione su cosa può fare foo() con questo valore, cioè con quali argomenti può chiamare do_something() . Ad esempio, questo non verrà compilato:

 fn foo< 'a>(b: Box>) { let x: usize = 10; b.do_something(&x); } 

Questo non verrà compilato perché le variabili locali hanno durate che sono strettamente inferiori alle durate specificate dai parametri di durata (penso sia chiaro il motivo per cui è così), quindi non è ansible chiamare b.do_something(&x) perché richiede il suo argomento avere lifetime 'a , che è strettamente superiore a quello di x .

Tuttavia, puoi farlo con la bar :

 fn bar(b: Box Trait< &'a usize>>) { let x: usize = 10; b.do_something(&x); } 

Funziona perché ora la bar può selezionare la durata necessaria invece del chiamante della bar .

Questo è importante quando usi chiusure che accettano riferimenti. Ad esempio, supponiamo di voler scrivere un metodo filter() su Option :

 impl Option { fn filter(self, f: F) -> Option where F: FnOnce(&T) -> bool { match self { Some(value) => if f(&value) { Some(value) } else { None } None => None } } } 

La chiusura qui deve accettare un riferimento a T perché altrimenti sarebbe imansible restituire il valore contenuto nell’opzione (questo è lo stesso ragionamento di filter() sugli iteratori).

Ma quale durata dovrebbe avere &T in FnOnce(&T) -> bool avere? Ricorda, non specificiamo le vite in firme di funzioni solo perché esiste elisione a vita in atto; in realtà il compilatore inserisce un parametro di durata per ogni riferimento all’interno di una firma di funzione. Ci dovrebbe essere una vita associata a &T in FnOnce(&T) -> bool . Quindi, il modo più “ovvio” per espandere la firma sopra sarebbe questo:

 fn filter< 'a, F>(self, f: F) -> Option where F: FnOnce(&'a T) -> bool 

Tuttavia, questo non funzionerà. Come nell’esempio precedente, il lifetime 'a è strettamente più lungo della vita di qualsiasi variabile locale in questa funzione, incluso il value all’interno dell’istruzione match. Pertanto, non è ansible applicare f a &value causa della mancata corrispondenza a vita. La suddetta funzione scritta con tale firma non verrà compilata.

D’altra parte, se espandiamo la firma di filter() questo modo (e questo è in realtà il modo in cui elision per la durata delle chiusure funziona ora in Rust):

 fn filter(self, f: F) -> Option where F: for< 'a> FnOnce(&'a T) -> bool 

quindi chiamare f con &value come argomento è perfettamente valido: possiamo scegliere la vita ora, quindi usare la vita di una variabile locale è assolutamente soddisfacente. Ecco perché le HRTB sono importanti: non sarai in grado di esprimere molti schemi utili senza di loro.

Puoi anche leggere un’altra spiegazione di HRTB in Nomicon .