2015-01-11 2 views
6

Я использую FFI для написания кода Rust против C API с сильными понятиями собственности (API libnotmuch, если это имеет значение).Как я могу ограничить время жизни структуры структурой структуры 'parent'?

Основная точка входа в API - это база данных; Я могу создавать объекты запроса из базы данных. Он предоставляет функции деструктора для баз данных и запросов (и множество других объектов).

Однако запрос не может пережить базу данных, из которой он был создан. Функция деструктора базы данных уничтожит любые недостроенные запросы и т. Д., После чего деструкторы запроса не работают.

До сих пор у меня есть основные части работы - я могу создавать базы данных и запросы и выполнять операции над ними. Но мне сложно кодировать границы жизни.

Я пытаюсь сделать что-то вроде этого:

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

У меня есть реализации Drop для каждого из них, которые называют основные функции C деструктора.

И тогда есть функция, которая создает запрос:

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

Я не знаю, что поставить вместо ? с тем чтобы запрос возвращал не может пережить Дб.

Как я могу моделировать ограничения времени жизни для этого API?

ответ

5

Если вы хотите привязать время жизни входного параметра к времени жизни возвращаемого значения, вам необходимо определить параметр lifetime для вашей функции и указать его в типах вашего входного параметра и возвращаемого значения. Вы можете указать любое имя, которое вы хотите для этого параметра времени жизни; часто, когда есть несколько параметров, мы просто назвать их 'a, 'b, 'c и т.д.

Вашего Db типа принимает параметр жизни, но это не должно: а Db не ссылается на существующий объект, так это не имеет ограничений продолжительности жизни.

Чтобы правильно заставить Db пережить Query, мы должны написать 'a на заимствованные указатель, а не от параметра продолжительности жизни на Db, что мы только что сняли.

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

Однако этого недостаточно. Если Newtypes не ссылается на их параметра 'a на всех, вы увидите, что на самом деле Query может пережить 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! 
    } 
} 

Это потому, что, по умолчанию, параметры прижизненных являются бивариантными, то есть компилятор может замените параметр более длинным или на более короткий срок службы, чтобы соответствовать требованиям вызывающего абонента.

При хранении заимствованного указателя в структурах, параметр времени жизни трактуется как контрвариантный: это означает, что компилятор может заменить параметр с более короткой продолжительностью жизнью, но не с более длительным сроком службой.

Мы можем попросить компилятор обработать ваш параметр пожизненного контрвариантной вручную путем добавления ContravariantLifetime маркера нашей структуры:

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

Теперь компилятор правильно отклоняет присвоение query, который переживет db.


Бонус: Если мы изменим create_query быть методом Db, а не свободная функции, мы можем воспользоваться правилами срока службы вывода компилятора и не писать 'a вообще на 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; 
    } 
} 

Когда метод имеет параметр self, компилятор предпочтет связать время жизни этого параметра с результатом, даже если есть другие параметры со временем жизни. Однако для бесплатных функций вывод возможен только в том случае, если только один параметр имеет продолжительность жизни. Здесь из-за параметра query_string, который имеет тип &'a str, существует 2 параметра со временем жизни, поэтому компилятор не может определить, с каким параметром мы хотим связать результат.

+0

Спасибо! С этим дизайном тестовая функция, которую я написал с плохим временем жизни, теперь корректно отвергается компилятором. –

Смежные вопросы