如何将结构的生命周期限制为 'parent' 结构的生命周期?

How can I constrain the lifetime of a struct to that of a 'parent' struct?

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

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

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

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

我正在尝试做这样的事情:

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<?>

我不知道用什么来代替 ?s,这样返回的 Query 就不会超过 Db。

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

当你想将输入参数的生命周期绑定到 return 值的生命周期时,你需要在你的函数上定义一个生命周期参数,并在你的输入参数和 return值。你可以给这个生命周期参数起任何你想要的名字;通常,当参数很少时,我们只是将它们命名为'a'b'c

您的 Db 类型采用生命周期参数,但它不应该:Db 不引用现有对象,因此它没有生命周期限制。

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

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

然而,这还不够。如果你的新类型根本不引用它们的 'a 参数,你会发现 Query 实际上可以比 Db:

Editor's note: This code no longer compiles since Rust 1.0. You must use 'a in some way in the body of Query.

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

那是因为,在 Rust 1.0 之前,生命周期参数是双变,即编译器可能会用更长的更短的参数替换参数生命周期以满足调用者的要求。

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

我们可以通过向我们的结构添加 PhantomData 标记来要求编译器手动将您的生命周期参数视为协变:

use std::marker::PhantomData;

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

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

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 的方法而不是自由函数,我们可以利用编译器的生命周期推理规则,而不是在 create_query:

上写 'a
use std::marker::PhantomData;

struct Db(*mut ());
struct Query<'a>(*mut (), PhantomData<&'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 (), PhantomData)
    }
}

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 参数时,编译器将更喜欢 link 将该参数的生命周期与结果相结合,即使还有其他具有生命周期的参数。但是,对于自由函数,只有在只有一个参数具有生命周期的情况下才有可能进行推理。这里,因为 query_string 参数是 &'a str 类型,所以有 2 个参数具有生命周期,所以编译器无法推断我们想要 link 结果的参数。