unsafe.md
commit 07aaca3a0724000e735a558d4c23b600512346d9
Rust主要魅力是它强大的静态行为保障。不过安全检查天性保守:有些程序实际上是安全的,不过编译器不能验证它是否是真的。为了写这种类型的程序,我们需要告诉编译器稍微放松它的限制。为此,Rust有一个关键字,unsafe
。使用unsafe
的代码比正常代码有更少的限制。
让我们过一遍语法,接着我们讨论语义。unsafe
用在两个上下文中。第一个标记一个函数为不安全的:
unsafe fn danger_will_robinson() {
// scary stuff
}
例如所有从[FFI](Foreign Function Interface 外部函数接口.md)调用的函数都必须标记为unsafe
。第二个unsafe
的用途是一个不安全块。
unsafe {
// scary stuff
}
第三个是不安全trait:
unsafe trait Scary { }
而第四个是impl
这些trait:
# unsafe trait Scary { }
unsafe impl Scary for i32 {}
显式勾勒出那些可能会有bug并造成大问题的代码是很重要的。如果一个Rust程序段错误了,你可以确认它位于标记为unsafe
部分的什么地方。
“安全”指什么?(What does ‘safe’ mean?)
安全,在Rust的上下文中,意味着“不做任何不安全的事”。不过也要明白,有一些特定的行为在你的代码中可能并不合意,但很明显并不是不安全的:
- 死锁
- 内存或其他资源的泄露
- 退出但未调用析构函数
- 整型溢出
Rust不能避免所有类型的软件错误。有bug的代码可能并将会出现在Rust中。这些事并不很光彩,不过它们并不特别的定义为unsafe
。
另外,如下列表全是 Rust 中的未定义行为,并且必须被避免,即便在编写unsafe
代码时:
- 数据竞争
- 解引用一个空/悬垂裸指针
- 读
undef
(未初始化)内存 - 使用裸指针打破指针重叠规则(pointer aliasing rules)
&mut T
和&T
遵循LLVM范围的noalias
模型,除了如果&T
包含一个UnsafeCell<U>
的话。不安全代码必须不能违反这些重叠(aliasing)保证- 不使用
UnsafeCell<U>
改变一个不可变值/引用 - 通过编译器固有功能调用未定义行为:
- 使用
std::ptr::offset
(offset
功能)来索引超过对象边界的值,除了允许的末位超出一个字节 - 在重叠(overlapping)缓冲区上使用
std::ptr::copy_nonoverlapping_memory
(memcpy32/memcpy64
功能)
- 使用
- 原生类型的无效值,即使是在私有字段/本地变量中:
- 空/悬垂引用或装箱
bool
中一个不是false
(0
)或true
(1
)的值enum
中一个并不包含在类型定义中判别式char
中一个代理字(surrogate)或超过char::MAX
的值str
中非UTF-8字节序列
- 在外部代码中使用Rust或在Rust中使用外部语言
不安全的超级力量(Unsafe Superpowers)
在不安全函数和不安全块,Rust将会让你做3件通常你不能做的事:只有3件。它们是:
- 访问和更新一个[静态可变变量](
const
andstatic
.md#static) - 解引用一个裸指针
- 调用不安全函数。这是最NB的能力
这就是全部。注意到unsafe
不能(例如)“关闭借用检查”是很重要的。为随机的Rust代码加上unsafe
并不会改变它的语义,它并不会开始接受任何东西。
不过确实它会让你写的东西打破一些规则。让我们按顺序过一遍这3个能力。