【问题标题】:Forcing the order in which struct fields are dropped强制删除结构字段的顺序
【发布时间】:2017-04-24 12:37:52
【问题描述】:

我正在实现一个对象,该对象拥有通过 FFI 从 C 库创建的多个资源。如果构造函数出现恐慌,为了清理已经完成的工作,我将每个资源包装在自己的结构中并为它们实现Drop。但是,在删除对象本身时,我不能保证资源会以安全的顺序被删除,因为 Rust 没有定义删除结构字段的顺序。

通常,您可以通过使对象不拥有资源而是借用它们来解决这个问题(这样资源可以相互借用)。实际上,这将问题推到了调用代码上,其中删除顺序被很好地定义并通过借用语义强制执行。但这不适合我的用例,一般来说有点逃避。

令人愤怒的是,如果 drop 出于某种原因使用 self 而不是 &mut self,这将非常容易。然后我可以按照我想要的顺序调用std::mem::drop

有没有办法做到这一点?如果没有,是否有任何方法可以在构造函数恐慌的情况下进行清理,而无需手动捕获和重新启动?

【问题讨论】:

  • 你能给出一些示例代码吗?提出一个有起点的建议会更容易。

标签: rust raii


【解决方案1】:

您可以通过两种方式指定结构字段的放置顺序:

隐式

我写了RFC 1857 指定删除订单,它已于 2017 年 7 月 3 日合并!根据 RFC,结构字段的删除顺序与声明的顺序相同。

您可以通过运行下面的示例来检查这一点

struct PrintDrop(&'static str);

impl Drop for PrintDrop {
    fn drop(&mut self) {
        println!("Dropping {}", self.0)
    }
}

struct Foo {
    x: PrintDrop,
    y: PrintDrop,
    z: PrintDrop,
}

fn main() {
    let foo = Foo {
        x: PrintDrop("x"),
        y: PrintDrop("y"),
        z: PrintDrop("z"),
    };
}

输出应该是:

Dropping x
Dropping y
Dropping z

明确

RFC 1860 引入了ManuallyDrop 类型,它包装了另一种类型并禁用了它的析构函数。这个想法是您可以通过调用特殊函数 (ManuallyDrop::drop) 手动删除对象。此函数不安全,因为在删除对象后内存未初始化。

您可以使用ManuallyDrop 在您的类型的析构函数中明确指定字段的放置顺序:

#![feature(manually_drop)]

use std::mem::ManuallyDrop;

struct Foo {
    x: ManuallyDrop<String>,
    y: ManuallyDrop<String>
}

impl Drop for Foo {
    fn drop(&mut self) {
        // Drop in reverse order!
        unsafe {
            ManuallyDrop::drop(&mut self.y);
            ManuallyDrop::drop(&mut self.x);
        }
    }
}

fn main() {
    Foo {
        x: ManuallyDrop::new("x".into()),
        y: ManuallyDrop::new("y".into())
    };
}

如果您需要此行为而无法使用任何一种较新的方法,请继续阅读...

drop 的问题

drop 方法不能按值获取其参数,因为该参数将在作用域的末尾再次被删除。这将导致该语言的所有析构函数无限递归。

可能的解决方案/解决方法

我在一些代码库中看到的一种模式是将要删除的值包装在 Option&lt;T&gt; 中。然后,在析构函数中,您可以将每个选项替换为 None,并以正确的顺序删除结果值。

例如,在scoped-threadpool crate 中,Pool 对象包含将安排新工作的线程和发送者。为了在删除时正确加入线程,应首先删除发送者,然后删​​除线程。

pub struct Pool {
    threads: Vec<ThreadData>,
    job_sender: Option<Sender<Message>>
}

impl Drop for Pool {
    fn drop(&mut self) {
        // By setting job_sender to `None`, the job_sender is dropped first.
        self.job_sender = None;
    }
}

人体工学说明

当然,以这种方式做事与其说是正确的解决方案,不如说是一种解决方法。此外,如果优化器无法证明该选项将始终为Some,那么您现在可以为每次访问您的结构字段提供一个额外的分支。

幸运的是,没有什么能阻止未来版本的 Rust 实现允许指定删除顺序的功能。它可能需要一个 RFC,但似乎肯定是可行的。问题跟踪器上有一个关于指定该语言的删除顺序的持续discussion,尽管它在上个月一直处于非活动状态。

安全注意事项

如果以错误的顺序销毁结构是不安全的,您可能应该考虑将它们的构造函数设为unsafe 并记录这一事实(以防您尚未这样做)。否则,仅通过创建结构并让它们超出范围就可能触发不安全行为。

【讨论】:

  • 特别是对于这种情况,C 值很可能是指针。这意味着您还可以实现drop 以仅在指针非NULL 时才释放它们。然后,在释放指针后将指针设置为 NULL,防止双重释放。和Option的思路一样。
  • @jmegaffin 刚刚更新了答案,以说明该语言在过去七个月中的发展。我希望你觉得它有用:)
猜你喜欢
  • 2012-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
  • 2017-08-19
  • 1970-01-01
  • 2014-05-03
相关资源
最近更新 更多