2015-01-11 4 views
6

Sto utilizzando l'FFI per scrivere un codice Rust contro un'API C con forti nozioni di proprietà (l'API libnotmuch, se ciò è importante).Come posso limitare la durata di una struttura a quella di una struttura "padre"?

Il punto di accesso principale all'API è un database; Posso creare oggetti Query dal Database. Fornisce funzioni di destructor per database e query (e molti altri oggetti).

Tuttavia, una query non può sopravvivere nel database da cui è stata creata. La funzione di distruzione del database distruggerà qualsiasi query non distrutta, ecc., Dopo di che i distruttori di query non funzionano.

Finora, ho i pezzi di base funzionanti: posso creare database e query e fare operazioni su di essi. Ma sto avendo difficoltà a codificare i limiti della vita.

sto cercando di fare qualcosa di simile:

struct Db<'a>(...) // newtype wrapping an opaque DB pointer 
struct Query<'a>(...) // newtype wrapping an opaque query pointer 

ho implementazioni di Drop per ciascuno di questi che chiamano funzioni della sottostante C distruttore.

e poi hanno una funzione che crea una query:

pub fun create_query<?>(db: &Db<?>, query_string: &str) -> Query<?> 

Non so cosa mettere al posto dei ? s in modo che la query ha restituito non è consentito di sopravvivere il Db.

Come è possibile modellare i vincoli di durata per questa API?

risposta

5

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.

+0

Grazie! Con questo design, la funzione di test che ho scritto con una brutta vita è ora correttamente rifiutata dal compilatore. –