如何将生命周期限制在一个代码区域?

问题描述

我正在为 C API 编写 Rust 绑定。这个特定的 C API 有两个函数f1f2f1 返回引用内部数据的句柄,该句柄在调用 f2 之前有效1

建模句柄生命周期约束的选项有哪些?最好在编译时强制执行,但如果这根本不可能,我也可以在运行时建立正确性。

解决方案可以假设以下限制:

我尝试过的事情

我曾考虑使用 PhantomData 标记结构,但在这里不起作用,因为我无权访问句柄引用的基础数据。

我尝试过的另一个选择是从公共 API 表面完全删除 f2,并让客户端将函数传递到 f1 中,该函数可以安全地假设有效句柄:

pub fn f1(f: fn(h: &Handle) -> ()) {
    let h = unsafe { api::f1() };
    // Execute client-provided code
    f(&h);
    unsafe { api::f2() };
}

虽然通过永远不允许 Handle 逃脱 f1(我认为)来强制执行生命周期约束,但感觉它从客户那里夺走了太多控制权。这是库代码,我不想把它变成一个框架。

我考虑过的另一种替代方法是让客户将句柄移至 f2 以将所有权转回库实现:

pub fn f2(_h: Handle) {
    unsafe { api::f2() };
}

这似乎也有效(我认为),尽管它在 f2 的签名中引入了一个看似无关的参数,从而导致 API 有点混乱。

问题

这里我看不到的(规范)解决方案是什么?


1f2 不是严格的清理代码。它因不同的原因被调用,并且只会使 f1 返回的引用作为副作用无效。

解决方法

您可以定义两个结构:

struct FirstState { ... }
struct SecondState { ... }

然后您将能够定义将相互转换的方法:

impl FirstState {
    // note: Takes ownership of self
    pub fn into_second(self) -> SecondState {
        api::f2();
        SecondState { ... }
    }
}

impl SecondState {
    // note: Takes ownership of self
    pub fn into_first(self) -> FirstState {
        api::f1();
        FirstState { ... }
    }
}

由于每次转换都会获得对象的所有权,因此您必须在调用 f1f2 之间交替进行。此外,您可以定义如下方法:

impl FirstState {
    pub fn get_internal_data(&self) -> &InternalData {
        ...
    }
}

get_internal_data 的签名强制返回的引用不能超过 FirstState,即使数据没有存储在 FirstState 本身中。