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)会使用它。)

有关“未定义行为”的更详细说明,请参阅参考

每当赋值、传递给函数/基本操作或从函数/基本操作返回时,都会“生成”一个值。

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

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

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

除此之外,Rust 对其他可疑操作相当宽容。Rust 认为以下操作是“安全的”

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

有关更详细的信息,请参阅参考

但是,任何实际设法做到这一点的程序可能都是不正确的。Rust 提供了许多工具来减少这些情况的发生,但从根本上防止这些问题被认为是不切实际的。