2015-01-11 8 views
6

Używam FFI do napisania kodu Rust przeciwko API C z silnymi pojęciami własności (API libnotmuch, jeśli to ma znaczenie).Jak mogę ograniczyć czas istnienia struktury do struktury "nadrzędnej"?

Głównym punktem wejścia do interfejsu API jest baza danych; Mogę tworzyć obiekty zapytań z bazy danych. Dostarcza funkcje destruktora dla baz danych i zapytań (oraz wiele innych obiektów).

Jednak zapytanie nie może przeżyć bazy danych, z której została utworzona. Funkcja destruktora bazy danych zniszczy wszelkie niezniszczone zapytania itp., Po których destruktery zapytań nie będą działać.

Do tej pory mam podstawowe elementy działające - mogę tworzyć bazy danych i zapytania oraz wykonywać na nich operacje. Ale mam trudności z zakodowaniem granic życia.

Próbuję zrobić coś takiego:

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

mam implementacje Drop dla każdego z tych, które nazywamy bazowe funkcje C destructor.

A potem mają funkcję, która tworzy zapytanie:

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

nie wiem, co umieścić w miejsce ? s tak, że zapytanie zwróciło nie może przeżyć DB.

Jak mogę modelować ograniczenia czasu życia dla tego interfejsu API?

Odpowiedz

5

Jeśli chcesz powiązać czas życia parametru wejściowego z czasem życia zwracanej wartości, musisz zdefiniować parametr cyklu życia dla swojej funkcji i odnieść go do typów parametrów wejściowych i wartości zwracanej. Możesz nadać dowolną nazwę temu parametrowi życia; Często, gdy istnieje kilka parametrów, po prostu wymienić je 'a, 'b, 'c itp

Twój typ Db przyjmuje parametr życiu, ale to nie powinno: a Db nie odnosi się do istniejącego obiektu, a więc nie ma ograniczeń na całe życie.

Aby poprawnie zmusić Db do przeżyje Query, musimy napisać 'a na pożyczonym wskaźnik, zamiast parametru dożywotnią na Db że po prostu usunięte.

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

To jednak za mało. Jeśli twoje newtypes nie odwoływać ich 'a parametr w ogóle, przekonasz się, że Query może faktycznie przeżyć 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! 
    } 
} 

To dlatego, że domyślnie parametry lifetime są bivariant, czyli kompilator może Parametr należy zastąpić dłuższym czasem trwania o mniejszą trwałość, aby spełnić wymagania osoby dzwoniącej.

Podczas przechowywania pożyczoną wskaźnik do struktury, parametr życia jest traktowana jako kontrawariantny: oznacza to, że kompilator może zastąpić parametr z krótszą żywotność, ale nie z dłuższą żywotnością.

Możemy poprosić kompilator traktować jako parametr dożywotnią kontrawariantny ręcznie dodając ContravariantLifetime marker do naszej struktury:

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

Teraz kompilator poprawnie odrzuca zadanie do query, które żyją dłużej db.


Bonus: Jeśli zmienimy create_query być metoda Db zamiast wolnej funkcji, możemy skorzystać z zasadami życia wnioskowania kompilator i nie pisać 'a w ogóle na 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; 
    } 
} 

Gdy metoda ma parametr self, kompilator będzie preferował łączenie czasu życia tego parametru z wynikiem, nawet jeśli istnieją inne parametry z czasem życia. Jednak w przypadku darmowych funkcji wnioskowanie jest możliwe tylko wtedy, gdy tylko jeden parametr ma całe życie. Tutaj, z powodu parametru query_string, który jest typu &'a str, istnieją 2 parametry z czasem życia, więc kompilator nie może określić, z którym parametrem chcemy powiązać wynik.

+0

Dzięki! Przy takim projekcie funkcja testu, którą napisałem ze złym czasem życia, jest teraz poprawnie odrzucana przez kompilator. –