强制删除struct字段的顺序

时间:2016-12-09 05:15:10

标签: rust raii

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

通常情况下,您可以通过设置它来解决这个问题,因此对象不拥有资源,而是借用它们(以便资源可以相互借用)。实际上,这会将问题推到调用代码中,其中drop order被很好地定义并且使用借用的语义来强制执行。但是这不适合我的用例,而且一般来说还有点罢工。

如果drop由于某种原因而self取代&mut self而不是std::mem::drop,那么令人难以置信的是,真是令人难以置信。然后我可以按照我想要的顺序拨打{{1}}。

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

1 个答案:

答案 0 :(得分:13)

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

隐式

我写了RFC 1857指定下降顺序,它被合并2017/07/03!根据RFC,struct字段的删除顺序与它们声明的顺序相同。

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

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<T>中。然后,在析构函数中,您可以用None替换每个选项,并以正确的顺序删除结果值。

例如,在scoped-threadpool包中,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,则现在每次访问struct字段时都有一个额外的分支。

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

关于安全的说明

如果以错误的顺序销毁你的结构是不安全的,你应该考虑制作他们的构造函数unsafe并记录这个事实(如果你还没有这样做)。否则,仅通过创建结构并让它们超出范围就可能触发不安全的行为。