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
部分的什么地方。
安全,在Rust的上下文中,意味着“不做任何不安全的事”。不过也要明白,有一些特定的行为在你的代码中可能并不合意,但很明显并不是不安全的:
Rust不能避免所有类型的软件错误。有bug的代码可能并将会出现在Rust中。这些事并不很光彩,不过它们并不特别的定义为unsafe
。
另外,如下列表全是 Rust 中的未定义行为,并且必须被避免,即便在编写unsafe
代码时:
undef
(未初始化)内存&mut T
和&T
遵循LLVM范围的noalias
模型,除了如果&T
包含一个UnsafeCell<U>
的话。不安全代码必须不能违反这些重叠(aliasing)保证UnsafeCell<U>
改变一个不可变值/引用std::ptr::offset
(offset
功能)来索引超过对象边界的值,除了允许的末位超出一个字节std::ptr::copy_nonoverlapping_memory
(memcpy32/memcpy64
功能)bool
中一个不是false
(0
)或true
(1
)的值enum
中一个并不包含在类型定义中判别式char
中一个代理字(surrogate)或超过char::MAX
的值str
中非UTF-8字节序列在不安全函数和不安全块,Rust将会让你做3件通常你不能做的事:只有3件。它们是:
const
and static
.md#static)这就是全部。注意到unsafe
不能(例如)“关闭借用检查”是很重要的。为随机的Rust代码加上unsafe
并不会改变它的语义,它并不会开始接受任何东西。
不过确实它会让你写的东西打破一些规则。让我们按顺序过一遍这3个能力。
static mut
Rust有一个叫static mut
的功能,它允许改变全局状态。这么做可能造成一个数据竞争,所以它天生是不安全的。关于更多细节,查看[静态量](const
and static
.md#static)部分。
裸指针让你做任意的指针算数,并会产生一系列不同的内存安全(safety & security)问题。在某种意义上,解引用一个任意指针的能力是你可以做的最危险的事之一。更多关于裸指针,查看[它的部分](Raw Pointers 裸指针.md)。
最后的能力能用于unsafe
的两个方面:你只能在一个不安全块中调用被标记为unsafe
的函数。
这个能力是强力和多变的。Rust暴露了一些作为不安全函数的[编译器固有功能](Intrinsics 固有功能.md),并且一些不安全函数绕开了安全检查,用安全换速度。
我在重复一遍:即便你可以在一个不安全块和函数中做任何事并不意味你应该这么做。编译器会表现得像你在保持它不变一样(The compiler will act as though you’re upholding its invariants),所以请小心。