Quando si desidera associare la durata di un parametro di input alla durata del valore di ritorno, è necessario definire un parametro di durata della funzione e fare riferimento a esso nei tipi del parametro di input e del valore di ritorno. Puoi dare qualsiasi nome tu voglia a questo parametro di durata; Spesso, quando ci sono alcuni parametri, dobbiamo solo nominarli 'a
, 'b
, 'c
, ecc
tuo tipo Db
prende un parametro di vita, ma non dovrebbe: un Db
non fa riferimento un oggetto esistente, in modo da non ha vincoli di durata.
Per forzare correttamente la Db
di sopravvivere il Query
, dobbiamo scrivere 'a
sul puntatore in prestito, piuttosto che sul parametro a vita su Db
che abbiamo appena rimosso.
pub fn create_query<'a>(db: &'a Db, query_string: &str) -> Query<'a>
Tuttavia, questo non è sufficiente. Se i Newtype non fanno riferimento il loro parametro 'a
a tutti, ci si accorge che un Query
può effettivamente sopravvivere un Db
:
struct Db(*mut());
struct Query<'a>(*mut()); // '
fn create_query<'a>(db: &'a Db, query_string: &str) -> Query<'a> { // '
Query(0 as *mut())
}
fn main() {
let query;
{
let db = Db(0 as *mut());
let q = create_query(&db, "");
query = q; // shouldn't compile!
}
}
Questo perché, per impostazione predefinita, i parametri di durata sono bivariant, vale a dire il compilatore può sostituire il parametro con uno o più lungo con una durata inferiore per soddisfare i requisiti del chiamante.
Quando si memorizza un puntatore in prestito in una struttura, il parametro di durata viene trattato come contravariant: questo significa che il compilatore può sostituire il parametro con una durata più breve, ma non con una vita più lunga.
possiamo chiedere al compilatore di trattare la vostra vita come parametro controvarianti manualmente con l'aggiunta di un marcatore ContravariantLifetime
al nostro struct:
use std::marker::ContravariantLifetime;
struct Db(*mut());
struct Query<'a>(*mut(), ContravariantLifetime<'a>);
fn create_query<'a>(db: &'a Db, query_string: &str) -> Query<'a> { // '
Query(0 as *mut(), ContravariantLifetime)
}
fn main() {
let query;
{
let db = Db(0 as *mut());
let q = create_query(&db, ""); // error: `db` does not live long enough
query = q;
}
}
Ora, il compilatore respinge correttamente il compito di query
, che sopravvive db
.
Bonus: Se cambiamo create_query
di essere un metodo di Db
, piuttosto che una funzione libera, siamo in grado di trarre vantaggio da regole di vita di inferenza del compilatore e non scrivere 'a
affatto su create_query
:
Quando un metodo ha un parametro self
, il compilatore preferirà collegare la durata di tale parametro con il risultato, anche se ci sono altri parametri con durata. Per le funzioni libere, tuttavia, l'inferenza è possibile solo se solo un parametro ha una durata. Qui, a causa del parametro query_string
, che è di tipo &'a str
, ci sono 2 parametri con una durata, quindi il compilatore non può inferire con quale parametro vogliamo collegare il risultato.
Grazie! Con questo design, la funzione di test che ho scritto con una brutta vita è ora correttamente rifiutata dal compilatore. –