强制删除struct字段的顺序

jme*_*fin 10 raii rust

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

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

令人气愤的是,如果因为某些原因而drop采取这种做法,这将非常容易.然后我可以按照我想要的顺序打电话.self&mut selfstd::mem::drop

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

aoc*_*via 14

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

我写了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"),
    };
}
Run Code Online (Sandbox Code Playgroud)

输出应该是:

Dropping x
Dropping y
Dropping z
Run Code Online (Sandbox Code Playgroud)

明确地

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())
    };
}
Run Code Online (Sandbox Code Playgroud)

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

丢弃的问题

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

关于人体工程学的说明

当然,以这种方式做事更像是一种解决方法,而不是一种适当的解决方案.此外,如果优化器无法证明该选项将始终存在Some,那么现在每次访问struct字段时都有一个额外的分支.

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

关于安全的说明

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

  • 特别是对于这种情况,C值可能是指针.这意味着你也可以实现`drop`来释放指针,只要它们是非'NULL`.然后,在释放它们之后将指针设置为NULL,从而防止双重释放.它与`Option`的想法相同. (2认同)