【问题标题】:How to constrain lifetimes to a region of code?如何将生命周期限制在代码区域?
【发布时间】:2020-12-22 08:53:27
【问题描述】:

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

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

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

  • 每次对f1 的调用都需要在再次调用f1 之前调用f2。换句话说,任何一个函数都不能有两个或多个连续调用。
  • 所有函数都从同一个线程调用。

我尝试过的事情

我曾研究过使用 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 返回的引用无效作为副作用。

【问题讨论】:

  • 这两种模式都相当常见并且支持您的用例。我建议最后一个选项。 “在调用 f2 之前有效的句柄...” 听起来与 消耗 函数调用完全一样,并且会在编译时传达句柄在之后无效。函数模式更具限制性,因为所有事情都必须一次完成(但根据预期用途,这可能更合适)。
  • 我认为第二个选项不会使 API 混乱。如果f2() 吃掉了句柄,它应该获得它的所有权,不管这是否会作为副作用发生。在我看来,它使 API 更清晰
  • 也许我误解了一些东西,但我没有看到第二个解决方案如何强制“每次调用 f1 都需要在再次调用 f1 之前调用 f2”;它允许不调用f2 而只是放下句柄。或者例如let h1 = f1(); let h2 = f1(); f2(h1); f2(h2);如果我的理解是正确的,我更喜欢选项 1,也许是 unsafe 版本的 f1f2 与选项 2。

标签: rust lifetime ffi


【解决方案1】:

你可以定义两个结构体:

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 本身内。

【讨论】:

  • 这与我的最终选择是否基本相同,即通过传递所有权手动终止生命周期?虽然我不确定我是否了解第一个 FirstState 对象来自哪里,或者在转换 into_second 之前有什么可以防止它被第二次调用。
  • 您必须构建自己的构造函数。无法在编译时验证您是否只构建其中一个,因此您能做的最好的事情就是在第二次创建时出现恐慌的全局。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多