您可以通过两种方式指定结构字段的放置顺序:
隐式
我写了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<T> 中。然后,在析构函数中,您可以将每个选项替换为 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 并记录这一事实(以防您尚未这样做)。否则,仅通过创建结构并让它们超出范围就可能触发不安全行为。