我正在使用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建模生命周期约束?
答案 0 :(得分:5)
如果要将输入参数的生命周期绑定到返回值的生命周期,则需要在函数上定义生命周期参数,并在输入参数和返回值的类型中引用它。您可以为此生命周期参数指定任何名称;通常,当参数很少时,我们只需将它们命名为'a
,'b
,'c
等。
您的Db
类型需要一个生命周期参数,但它不应该:Db
没有引用现有对象,因此它没有生命周期约束。
要正确强制Db
比Query
更长,我们必须在借用的指针上写'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个参数具有生命周期,因此编译器无法推断出我们想要将结果链接到哪个参数。