如何将结构的生命周期约束为“父”结构的生命周期?

时间:2015-01-11 20:35:33

标签: rust lifetime

我正在使用FFI针对具有强烈所有权概念的C API编写一些Rust代码(libnotmuch API,如果这很重要的话)。

API的主要入口点是数据库;我可以从数据库创建Query对象。它为数据库和查询(以及许多其他对象)提供析构函数。

但是,查询不能超过创建它的数据库。数据库析构函数将销毁任何未被破坏的查询等,之后查询析构函数不起作用。

到目前为止,我已经完成了基本工作 - 我可以创建数据库和查询,并对它们进行操作。但是我在编码生命周期边界时遇到了困难。

我试图做这样的事情:

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

我为每个调用底层C析构函数的Drop实现了。

然后有一个创建查询的函数:

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

我不知道应该用什么来代替?,以便返回的查询不允许超过Db。

如何为此API建模生命周期约束?

1 个答案:

答案 0 :(得分:5)

如果要将输入参数的生命周期绑定到返回值的生命周期,则需要在函数上定义生命周期参数,并在输入参数和返回值的类型中引用它。您可以为此生命周期参数指定任何名称;通常,当参数很少时,我们只需将它们命名为'a'b'c等。

您的Db类型需要一个生命周期参数,但它不应该:Db没有引用现有对象,因此它没有生命周期约束。

要正确强制DbQuery更长,我们必须在借用的指针上写'a,而不是在我们刚删除的Db上的生命周期参数上

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

但是,这还不够。如果你的新类型根本没有引用他们的'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!
    }
}

这是因为,默认情况下,生命周期参数是 bivariant ,即编译器可以用更长的替换参数,以便满足来电者的要求。

当您在结构中存储借来的指针时,生命周期参数被视为逆变:这意味着编译器可以用较短的生命周期替换参数,但不能使用更长的生命周期。

我们可以要求编译器通过向我们的结构添加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更长。


Bonus:如果我们将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个参数具有生命周期,因此编译器无法推断出我们想要将结果链接到哪个参数。