mem::uninitialized
的文档指出为什么使用该函数是危险/不安全的:在未初始化的内存上调用 drop
是未定义的行为。
所以我认为这段代码应该是未定义的:
let a: TypeWithDrop = unsafe { mem::uninitialized() };
panic!("=== Testing ==="); // Destructor of `a` will be run (U.B)
但是,我编写的这段代码可以在安全的 Rust 中运行,并且似乎不会出现未定义的行为:
#![feature(conservative_impl_trait)]
trait T {
fn disp(&mut self);
}
struct A;
impl T for A {
fn disp(&mut self) { println!("=== A ==="); }
}
impl Drop for A {
fn drop(&mut self) { println!("Dropping A"); }
}
struct B;
impl T for B {
fn disp(&mut self) { println!("=== B ==="); }
}
impl Drop for B {
fn drop(&mut self) { println!("Dropping B"); }
}
fn foo() -> impl T { return A; }
fn bar() -> impl T { return B; }
fn main() {
let mut a;
let mut b;
let i = 10;
let t: &mut T = if i % 2 == 0 {
a = foo();
&mut a
} else {
b = bar();
&mut b
};
t.disp();
panic!("=== Test ===");
}
它似乎总是执行正确的析构函数,而忽略另一个。如果我尝试使用 a
或 b
(例如 a.disp()
而不是 t.disp()
)它正确地错误地指出我可能正在使用未初始化的内存。令我惊讶的是,当 panic
king 时,无论 i
的值是多少,它总是运行正确的析构函数(打印预期的字符串)。
这是怎么发生的?如果运行时可以确定要运行哪个析构函数,是否应该从 mem::uninitialized()
的文档中删除有关强制需要为实现了 Drop
的类型初始化内存的部分如上链接?
最佳答案
使用 drop flags .
Rust(直到并包括版本 1.12)在其类型实现 Drop
的每个值中存储一个 bool 标志(因此将该类型的大小增加一个字节)。该标志决定是否运行析构函数。因此,当您执行 b = bar()
时,它会为 b
变量设置标志,因此只运行 b
的析构函数。反之亦然,a
。
请注意,从 Rust 版本 1.13(在编写 beta 编译器时)开始,该标志不再存储在类型中,而是存储在每个变量或临时变量的堆栈中。 Rust 编译器中 MIR 的出现使这成为可能。 MIR 显着简化了 Rust 代码到机器代码的翻译,从而使该功能能够将丢弃标志移动到堆栈。如果优化可以在编译时确定何时删除哪个对象,则通常会消除该标志。
您可以通过查看类型的大小在 1.12 版之前的 Rust 编译器中“观察”此标志:
struct A;
struct B;
impl Drop for B {
fn drop(&mut self) {}
}
fn main() {
println!("{}", std::mem::size_of::<A>());
println!("{}", std::mem::size_of::<B>());
}
在堆栈标志之前分别打印 0
和 1
,在堆栈标志之前打印 0
和 0
。
但是,使用 mem::uninitialized
仍然不安全,因为编译器仍然看到对 a
变量的赋值并设置丢弃标志。因此析构函数将在未初始化的内存上调用。请注意,在您的示例中, Drop
impl 不会访问您类型的任何内存(除了 drop 标志,但您不可见)。因此,您没有访问未初始化的内存(无论如何,它的大小为零字节,因为您的类型是零大小的结构)。据我所知,这意味着您的 unsafe { std::mem::uninitialized() }
代码实际上是安全的,因为之后不会发生内存不安全。
关于rust - Rust 如何知道在堆栈展开期间是否运行析构函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39750841/