问题描述
我正在为 C API 编写 Rust 绑定。这个特定的 C API 有两个函数:f1
和 f2
。 f1
返回引用内部数据的句柄,该句柄在调用 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 { ... }
}
}
由于每次转换都会获得对象的所有权,因此您必须在调用 f1
和 f2
之间交替进行。此外,您可以定义如下方法:
impl FirstState {
pub fn get_internal_data(&self) -> &InternalData {
...
}
}
get_internal_data
的签名强制返回的引用不能超过 FirstState
,即使数据没有存储在 FirstState
本身中。