它很安全,甚至还有箱子(现在找不到)。
但是。
在编写不安全的代码时,你必须非常小心。如果你不知道自己在做什么,很容易导致 UB。
例如,这里有一些你可能没有想到的东西:恐慌安全。
假设我们简单地实现它:
pub fn modify<T, F: FnOnce(T) -> T>(v: &mut T, f: F) {
let prev = unsafe { std::ptr::read(v) };
let new = f(prev);
unsafe { std::ptr::write(v, new) };
}
非常正确。
是吗?
fn main() {
struct MyStruct(pub i32);
impl Drop for MyStruct {
fn drop(&mut self) {
println!("MyStruct({}) dropped", self.0);
}
}
let mut v = MyStruct(123);
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
modify(&mut v, |_prev| {
// `prev` is dropped here.
panic!("Haha, evil panic!");
})
}))
.unwrap_err();
v.0 = 456; // Writing to an uninitialized memory!
// `v` is dropped here, double drop!
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6f7312a8be70cd43cf5cf7a9816be56a
我使用了一个自定义类型,它的析构函数除了打印之外什么都不做,但想象一下如果这是一个释放内存的Vec 并且我们正在写入释放的内存(然后,作为奖励,得到一个双-免费)。
正如@Kendas 所说,当没有中断点时,在 Rust 中让内存处于未初始化状态是有效的。问题是,实际上中断点比您希望的要多得多。事实上,在编写不安全的代码时,您必须考虑对外部代码的任何调用(即不是您的代码,也不是您相信不会做坏事的代码,例如std)是一个中断点。
不安全的代码很难。最好留在安全的地方。
编辑:您可能想知道AssertUnwindSafe 是什么。也许您甚至尝试删除它并注意到它没有编译。好吧,UnwindSafe 是针对这种情况的保护,AssertUnwindSafe 是一种绕过保护的方法。
你可能会问,这有什么意义?关键是,这种保护确实不准确。如此不准确,绕过它甚至不需要不安全。但它仍然存在,所以我们意外UB的可能性较低。
作为 API 的编写者,这对你来说并不重要 - 你应该表现得好像这种保护不存在,因为绕过它是安全的,而且很容易被错误地绕过。 Rust 标准库本身在过去也有类似的错误 (#86443, #81740, ... - 它们都在同一个代码中并非偶然 - 这些问题往往以块的形式出现。但是有'更多)。