2015-01-11 9 views
6

Ich benutze die FFI, um einige Rust-Code gegen eine C-API mit starken Vorstellungen von Besitz zu schreiben (die libnotmuch API, wenn das wichtig ist).Wie kann ich die Lebensdauer einer Struktur auf die einer übergeordneten Struktur beschränken?

Der Haupteingangspunkt der API ist eine Datenbank; Ich kann Query-Objekte aus der Datenbank erstellen. Es bietet Destruktor-Funktionen für Datenbanken und Abfragen (und viele andere Objekte).

Eine Abfrage kann jedoch die Datenbank, aus der sie erstellt wurde, nicht überleben. Die Datenbankdestruktorfunktion wird alle nicht zerstörten Abfragen usw. zerstören, nach denen die Abfragedestruktoren nicht funktionieren.

Bisher habe ich die grundlegenden Stücke arbeiten - ich kann Datenbanken und Abfragen erstellen und Operationen auf ihnen. Aber ich habe Schwierigkeiten, die Lebensdauergrenzen zu kodieren.

Ich versuche, so etwas zu tun:

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

Ich habe Implementierungen von Drop für jede dieser, dass die zugrunde liegenden C destructor Funktionen aufrufen.

Und haben dann eine Funktion, die eine Abfrage erstellt:

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

Ich weiß nicht, was anstelle des ? s zu setzen, so dass die Abfrage zurück nicht erlaubt ist, die Db zu überleben.

Wie kann ich die Lebensdauerbeschränkungen für diese API modellieren?

Antwort

5

Wenn Sie die Lebensdauer eines Eingabeparameters an die Lebensdauer des Rückgabewerts binden möchten, müssen Sie einen Lebensdauerparameter für Ihre Funktion definieren und ihn in den Typen des Eingabeparameters und Rückgabewerts referenzieren. Sie können jedem gewünschten Parameter einen beliebigen Namen geben; oft, wenn einige Parameter gibt, nennen wir sie einfach 'a, 'b, 'c usw.

Ihre Db Art ein Leben lang Parameter nimmt, aber es sollte nicht: a Db nicht ein bestehendes Objekt referenziert, so dass es hat keine Lebensdauerbeschränkungen.

Für eine korrekte Db zwingen, die Query zu überleben, wir 'a auf den geliehenen Zeiger, anstatt auf die Lebensdauer Parameter auf Db, die wir entfernt schreiben müssen.

pub fn create_query<'a>(db: &'a Db, query_string: &str) -> Query<'a> 

Das ist jedoch nicht genug. Wenn Ihr newtypes verweisen nicht auf ihre 'a Parameter überhaupt, werden Sie feststellen, dass ein Query tatsächlich ein überleben kann 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! 
    } 
} 

Das liegt daran, die standardmäßig Lebensdauer Parameter bivariant, dh der Compiler sind, können Ersetzen Sie den Parameter mit einer längeren oder eine kürzere Lebensdauer, um die Anforderungen des Anrufers zu erfüllen.

Wenn Sie einen ausgeliehenen Zeiger in einer Struktur speichern, wird der Lebensdauerparameter wie kontravariant behandelt: das bedeutet, dass der Compiler den Parameter mit einer kürzeren Lebensdauer, aber nicht mit einer längeren Lebensdauer ersetzen kann.

Wir können den Compiler fragen Sie Ihren Lebenszeit-Parameter als kontra durch Hinzufügen eines ContravariantLifetime Marker zu unserer Struktur manuell zu behandeln:

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

Nun wird der Compiler lehnt richtig die Zuordnung zu query, die db überlebt.


Bonus: Wenn wir create_query ändern ein Verfahren zur Herstellung Db, eher zu sein, als eine freie Funktion, können wir die Vorteile der Lebensdauer Inferenzregeln des Compilers nehmen und 'a nicht schreiben überhaupt auf create_query:

use std::marker::ContravariantLifetime; 

struct Db(*mut()); 
struct Query<'a>(*mut(), ContravariantLifetime<'a>); 

impl Db { 
    //fn create_query<'a>(&'a self, query_string: &str) -> Query<'a> 
    fn create_query(&self, query_string: &str) -> Query { 
     Query(0 as *mut(), ContravariantLifetime) 
    } 
} 

fn main() { 
    let query; 
    { 
     let db = Db(0 as *mut()); 
     let q = db.create_query(""); // error: `db` does not live long enough 
     query = q; 
    } 
} 

Wenn eine Methode über einen self-Parameter verfügt, wird der Compiler es vorziehen, die Lebensdauer dieses Parameters mit dem Ergebnis zu verknüpfen, auch wenn andere Parameter mit Lebensdauern vorhanden sind. Für freie Funktionen ist eine Inferenz jedoch nur möglich, wenn nur ein Parameter eine Lebensdauer hat. Wegen des Parameters query_string, der vom Typ &'a str ist, gibt es 2 Parameter mit einer Lebensdauer, sodass der Compiler nicht ableiten kann, mit welchem ​​Parameter das Ergebnis verknüpft werden soll.

+0

Danke! Mit diesem Entwurf wird die Testfunktion, die ich mit schlechter Lebensdauer geschrieben habe, jetzt vom Compiler korrekt zurückgewiesen. –