集册 Rust 编程语言 不安全代码

不安全代码

欢马劈雪     最近更新时间:2020-08-04 05:37:59

395

unsafe.md
commit 07aaca3a0724000e735a558d4c23b600512346d9

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部分的什么地方。

“安全”指什么?(What does ‘safe’ mean?)

安全,在Rust的上下文中,意味着“不做任何不安全的事”。不过也要明白,有一些特定的行为在你的代码中可能并不合意,但很明显并不是不安全的:

  • 死锁
  • 内存或其他资源的泄露
  • 退出但未调用析构函数
  • 整型溢出

Rust不能避免所有类型的软件错误。有bug的代码可能并将会出现在Rust中。这些事并不很光彩,不过它们并不特别的定义为unsafe

另外,如下列表全是 Rust 中的未定义行为,并且必须被避免,即便在编写unsafe代码时:

  • 数据竞争
  • 解引用一个空/悬垂裸指针
  • undef(未初始化)内存
  • 使用裸指针打破指针重叠规则(pointer aliasing rules)
  • &mut T&T遵循LLVM范围的noalias模型,除了如果&T包含一个UnsafeCell<U>的话。不安全代码必须不能违反这些重叠(aliasing)保证
  • 不使用UnsafeCell<U>改变一个不可变值/引用
  • 通过编译器固有功能调用未定义行为:
    • 使用std::ptr::offsetoffset功能)来索引超过对象边界的值,除了允许的末位超出一个字节
    • 在重叠(overlapping)缓冲区上使用std::ptr::copy_nonoverlapping_memorymemcpy32/memcpy64功能)
  • 原生类型的无效值,即使是在私有字段/本地变量中:
    • 空/悬垂引用或装箱
    • bool中一个不是false0)或true1)的值
    • enum中一个并不包含在类型定义中判别式
    • char中一个代理字(surrogate)或超过char::MAX的值
    • str中非UTF-8字节序列
  • 在外部代码中使用Rust或在Rust中使用外部语言

不安全的超级力量(Unsafe Superpowers)

在不安全函数和不安全块,Rust将会让你做3件通常你不能做的事:只有3件。它们是:

  1. 访问和更新一个[静态可变变量](const and static.md#static)
  2. 解引用一个裸指针
  3. 调用不安全函数。这是最NB的能力

这就是全部。注意到unsafe不能(例如)“关闭借用检查”是很重要的。为随机的Rust代码加上unsafe并不会改变它的语义,它并不会开始接受任何东西。

不过确实它会让你写的东西打破一些规则。让我们按顺序过一遍这3个能力。

访问和更新一个static mut

展开阅读全文