原文链接:https://doc.rust-lang.org/nomicon/what-unsafe-does.html

非安全Rust能做什么

非安全Rust比安全Rust可以多做的事情只有以下几个:

  • 解引用裸指针
  • 调用非安全函数(包括C语言函数,编译器内联函数,还有直接内存分配等)
  • 实现非安全trait
  • 访问或修改可变静态变量
  • 访问union的字段

就这些。这些操作被归为非安全的,是因为使用不正确就会导致可怕的未定义行为。一旦触发了未定义行为,编译器就可以放飞自我,肆意破坏你的程序。切记,一定不能给未定义行为任何的机会。

与C不同,Rust充分限制了可能出现的未定义行为的种类。语言核心只需要防止这几种行为:

  • 解引用(使用*操作符)悬垂指针或者未赋值的指针(见下文)
  • 破坏指针混淆规则
  • 使用错误的调用ABI调用函数或使用错误的展开ABI从函数展开
  • 引起竞争条件
  • 执行使用target_feature编译的代码,但当前执行线程不支持该代码
  • 创建无效值(单独或作为复合类型的字段,例如enum/struct/array/tuple):
    • 0和1以外的bool类型值
    • 一个带有无效判别式的enum
    • 空的fn指针
    • 在[0x0,0xD&FF]和[0xE000, 0x10FFFF]范围以外的char类型值
    • 一个!(所有值对该类型均无效)
    • 整数(i*/u*),浮点值(f*)或从未初始化的内存中读取的原始指针,或在str中的未初始化的内存
    • 悬垂,未赋值或指向无效值的应用/Box
    • 宽引用,Box或具有无效元数据的原始指针:
      • 如果dyn Trait元数据不是指向Trait的vtable的指针,该指针与引用或指针所指向的实际动态trait相匹配,则该元数据无效
      • 如果切片长度不是有效的usize,则切片元数据无效(即,不得从未初始化的内存中读取)
    • 具有自定义无效值的类型,这些无效值是空值(null)之一,例如NonNull(请求自定义无效值是一个不稳定的功能,但是某些稳定的libstd类型,如NonNull,会使用它)

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

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

只有这些。Rust语言自身可以导致未定义行为的操作就只有这些。当然,非安全函数和trait可以声明自己专有的安全规范,要求开发者必须遵守以避免未定义行为。比如,allocator API声明回收一段未分配的内存是未定义行为。

但是,违背这些专有的规范通常也只是间接地触发上面列出的行为。另外,编译器内联函数也可能引入一些规则,一般是针对代码优化的假设条件。比如,Vec和Box使用的内联函数要求传入的指针永远不能为null。

Rust对于一些模糊的操作则通常比较宽容。Rust会认为下列操作是安全的:

  • 死锁
  • 竞争条件
  • 内存泄漏
  • 调用析构函数失败
  • 整型值溢出
  • 终止程序
  • 删除生产数据库

当然,有以上行为的程序极有可能就是错误的。Rust提供了一系列的工具减少这种事情的发生,但是完全地杜绝它们其实是不现实的。