我正在使用FFI针对具有强大所有权概念的C API编写一些Rust代码(libnotmuch API,如果这很重要).
API的主要入口点是数据库; 我可以从数据库创建Query对象.它为数据库和查询(以及许多其他对象)提供析构函数.
但是,Query不能比创建它的数据库寿命更长.数据库析构函数将销毁任何未销毁的查询等,之后查询析构函数不起作用.
到目前为止,我已经完成了基本的工作 - 我可以创建数据库和查询,并对它们进行操作.但是我在编码生命周期边界时遇到了困难.
我正在尝试做这样的事情:
struct Db<'a>(...) // newtype wrapping an opaque DB pointer
struct Query<'a>(...) // newtype wrapping an opaque query pointer
Run Code Online (Sandbox Code Playgroud)
我Drop为每个调用底层C析构函数的实现.
然后有一个创建查询的函数:
pub fun create_query<?>(db: &Db<?>, query_string: &str) -> Query<?>
Run Code Online (Sandbox Code Playgroud)
我不知道应该用什么代替?s,以便返回的Query不允许比Db更长.
如何为此API建模生命周期约束?
如果要将输入参数的生存期绑定到返回值的生存期,则需要在函数上定义生命周期参数,并在输入参数和返回值的类型中引用它.您可以为此生命周期参数指定任何名称; 通常,当有几个参数,我们只是他们的名字'a,'b,'c,等.
您的Db类型需要一个生命周期参数,但它不应该:a Db不引用现有对象,因此它没有生命周期约束.
为了正确地强制Db使其寿命更长Query,我们必须'a在借用的指针上写,而不是在Db我们刚删除的生命周期参数上.
pub fn create_query<'a>(db: &'a Db, query_string: &str) -> Query<'a>
Run Code Online (Sandbox Code Playgroud)
但是,这还不够.如果你的新类型根本没有引用它们的'a参数,你会发现a Query实际上可以比a 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!
}
}
Run Code Online (Sandbox Code Playgroud)
这是因为,在默认情况下,寿命参数是双变量,即编译器可以用较长的替换参数或为了满足来电者的要求更短的寿命.
将借用指针存储在结构中时,将lifetime参数视为逆变:这意味着编译器可以使用较短的生命周期替换参数,但不能使用较长的生命周期.
我们可以要求编译器通过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;
}
}
Run Code Online (Sandbox Code Playgroud)
现在,编译器正确地拒绝了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;
}
}
Run Code Online (Sandbox Code Playgroud)
当方法具有self参数时,编译器将优先将该参数的生命周期与结果链接,即使存在其他具有生命周期的参数.但是对于自由函数,仅当只有一个参数具有生命周期时才可以进行推理.这里,由于query_string参数是类型的&'a str,有2个参数具有生命周期,因此编译器无法推断我们想要将结果链接到哪个参数.