不安全 Rust 能做什么

不安全 Rust 中唯一不同的是,你可以:

  • 解引用原始指针
  • 调用 unsafe 函数 (包括 C 函数、编译器内置函数和原始分配器)
  • 实现 unsafe 特性
  • 访问或修改可变静态变量
  • 访问 union 的字段

就这些。这些操作被降级为不安全的原因是,滥用其中任何一项都会导致可怕的未定义行为。调用未定义行为会使编译器有权对你的程序做任意糟糕的事情。你绝对 *不应该* 调用未定义行为。

与 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 vtable 的指针,并且该 vtable 与指针或引用指向的实际动态特性相匹配,则该元数据无效。
      • 如果长度不是有效的 usize (即,它不能从未初始化的内存中读取),则切片元数据无效。
    • 具有自定义无效值且为这些值之一的类型,例如为空的NonNull。(请求自定义无效值是一项不稳定的特性,但一些稳定的 libstd 类型(如 NonNull)会使用它。)

有关“未定义行为”的更详细说明,你可以参考参考文档

每当赋值、传递给函数/原始操作或从函数/原始操作返回时,都会发生“产生”值。

如果引用/指针为空,或者它指向的所有字节不属于同一分配(因此,它们都必须属于 *某个* 分配),则该引用/指针是“悬空的”。它指向的字节跨度由指针值和被指向类型的大小决定。因此,如果跨度为空,“悬空”与“空”相同。请注意,切片和字符串指向它们的整个范围,因此,长度元数据永远不要太大非常重要(特别是,分配以及因此切片和字符串的大小不能超过 isize::MAX 字节)。如果出于某种原因这太麻烦了,请考虑使用原始指针。

就这些。这就是 Rust 中所有导致未定义行为的原因。当然,不安全函数和特性可以自由地声明程序必须维护以避免未定义行为的任意其他约束。例如,分配器 API 声明释放未分配的内存是未定义行为。

但是,违反这些约束通常只会传递性地导致上述问题之一。一些额外的约束可能也源于编译器内在函数,这些内在函数对如何优化代码做出特殊假设。例如,Vec 和 Box 使用了内在函数,这些内在函数要求它们的指针始终为非空。

在其他可疑操作方面,Rust 则相当宽松。Rust 认为以下操作是“安全”的:

  • 死锁
  • 具有竞争条件
  • 内存泄漏
  • 整数溢出 (使用内置运算符,例如 + 等)
  • 中止程序
  • 删除生产数据库

有关更详细的信息,你可以参考参考文档

但是,任何实际上设法做到这种事情的程序 *可能* 都是不正确的。Rust 提供了很多工具来使这些事情很少发生,但这些问题被认为在分类上难以预防。