Unsafe Rust 能做什么
Unsafe Rust 中唯一不同的是,你可以
- 解引用原始指针
- 调用
unsafe
函数(包括 C 函数、编译器内部函数和原始分配器) - 实现
unsafe
trait - 修改静态变量
- 访问
union
的字段
就是这样。这些操作被归入 Unsafe 的原因是,滥用其中任何一项都会导致可怕的未定义行为。调用未定义行为会赋予编译器对你的程序进行任意破坏的权利。你绝对不应该调用未定义行为。
与 C 语言不同,Rust 中未定义行为的范围非常有限。核心语言所关心的只是防止以下情况发生
- 解引用(对悬空指针或未对齐指针使用
*
运算符)(见下文) - 违反指针别名规则
- 使用错误的调用 ABI 调用函数,或从使用错误的展开 ABI 的函数中展开。
- 导致数据竞争
- 执行使用当前执行线程不支持的目标特性编译的代码
- 生成无效值(单独生成或作为复合类型(如
enum
/struct
/数组/元组)的字段生成)- 不是 0 或 1 的
bool
- 具有无效判别式的
enum
- 空
fn
指针 - 超出 [0x0, 0xD7FF] 和 [0xE000, 0x10FFFF] 范围的
char
!
(此类型的所有值均无效)- 从未初始化内存读取的整数(
i*
/u*
)、浮点数(f*
)或原始指针,或str
中的未初始化内存。 - 悬空、未对齐或指向无效值的引用/
Box
。 - 具有无效元数据的宽引用、
Box
或原始指针- 如果
dyn Trait
元数据不是指向与指针或引用实际指向的动态 trait 匹配的Trait
的 vtable 的指针,则该元数据无效 - 如果长度不是有效的
usize
(即,它不能从未初始化的内存中读取),则切片元数据无效
- 如果
- 具有自定义无效值的类型,并且该类型是这些值之一,例如为空的
NonNull
。(请求自定义无效值是一个不稳定的特性,但一些稳定的 libstd 类型(如NonNull
)会使用它。)
- 不是 0 或 1 的
有关“未定义行为”的更详细说明,请参阅参考。
每当赋值、传递给函数/基本操作或从函数/基本操作返回时,都会“生成”一个值。
如果引用/指针为空,或者它指向的所有字节并非都属于同一个分配区域(因此,它们尤其必须都属于某个分配区域),则该引用/指针“悬空”。它指向的字节范围由指针值和指针类型的大小决定。因此,如果范围为空,“悬空”与“空”相同。请注意,切片和字符串指向它们的整个范围,因此长度元数据永远不能太大(特别是,分配区域以及切片和字符串的大小不能超过 isize::MAX
字节)。如果由于某种原因这太麻烦,请考虑使用原始指针。
就是这样。这就是 Rust 中所有导致未定义行为的原因。当然,unsafe 函数和 trait 可以自由声明程序必须维护的任意其他约束,以避免未定义行为。例如,分配器 API 声明释放未分配的内存是未定义行为。
但是,违反这些约束通常只会间接导致上述问题之一。一些额外的约束也可能来自编译器内部函数,这些内部函数对代码的优化方式做出了特殊的假设。例如,Vec 和 Box 使用的内部函数要求它们的指针始终为非空。
除此之外,Rust 对其他可疑操作相当宽容。Rust 认为以下操作是“安全的”
- 死锁
- 存在竞态条件
- 内存泄漏
- 整数溢出(使用内置运算符,如
+
等) - 中止程序
- 删除生产数据库
有关更详细的信息,请参阅参考。
但是,任何实际设法做到这一点的程序可能都是不正确的。Rust 提供了许多工具来减少这些情况的发生,但从根本上防止这些问题被认为是不切实际的。