unsafe

unsafe    

Rust 的主要缺点是其对行为的强大的静态担保。但安全检查是保守的:有些程序实际上是安全的,但是编译器无法证实这是真的。为了写这种程序,我们需要告诉编译器放宽限制。为此, Rust 有一个关键词,unsafe。代码使用 unsafe 比正常的代码有更少的限制。    

让我们复习语法,然后我们将讨论语义。unsafe 在两种情况下被使用。第一个情况是标记一个函数为不安全:

unsafe fn danger_will_robinson() {
// scary stuff 
}

例如,所有函数调用 FFI 时必须标记为 unsafe。第二个使用 unsafe 情况是不安全块:

unsafe {
// scary stuff
}

能够明确划定可能有漏洞的代码是非常重要的,并且这些漏洞能造成大问题。如果 Rust 程序段错误,你能确定这部分中哪里标记为 unsafe。

“safe”是什么意思?

在 Rust 的上下文中,safe 的意思是“不做任何不安全的事。” 这很简单!     

好吧,让我们再试一次:什么是不安全的?这里有一个列表:

  • 数据竞争  
  • 非关联化空的或悬空的原始指针  
  • 读取 undef(未初始化的)内存  
  • 打破原始指针的指针别名规则。  
  • &mut T 和 &T 遵循LLVM的作用域 noalias 模式,除非 &T 包含一个 UnsafeCell<U>。不安全的代码必须不违反这些别名担保。  
  • 在没有 UnsafeCell<U> 的情况下,改变一个不可变的值/引用
  • 通过这些编译器特性调用未定义的行为:   
    • 有 std::ptr::offset 的对象的越界索引,除了一个字节结束过去这是允许的。  
    • 在重叠的缓冲区时使用 std::ptr::copy_nonoverlapping_memory (memcpy32/memcpy64 特性) 
  • 原始类型的无效值,即使在私有作用域/局部:   
    • 空/悬空的引用或盒子 
    • 在一个 bool 中除了 false (0) 或 true (1) 的值 
    • 在一个不包括类型定义的 enum 的一个判别式
    • 在一个大于或等于 char::MAX 的 char 里的一个值
    • 在一个 str 里的 Non-UTF-8 类型序列 
    • 从外部代码展开到 Rust 或 从 Rust 展开到到外部代码。

这是很多东西。注意到各种各样的不好的但没有标记为 unsafe 的行为是很重要的。

  • 死锁  
  • 从私有作用域读取数据 
  • 由于引用计数周期引起的泄漏  
  • 没有调用析构函数的情况下退出  
  • 发送信号  
  • 访问/修改文件系统 
  • 整数溢出

Rust 不能防止各种各样的软件问题。bug 代码可以并将写在 Rust。这些行为并不好,但他们不符合 unsafe。

Usafe 超级能力    

在不安全的函数和不安全的代码块内,Rust 通常会让你做三件通常不会做的事。以下就是这三件事:

  • 访问或更新一个静态可变的变量。  
  • 解除引用原始指针。  
  • 调用不安全的函数。这是最强大的能力。    

就这样。重要的是,例如, unsafe 不会“关掉借用查器”。将 unsafe 添加到一些随机 Rust 代码并没有改变其语义,它不会开始接受任何东西。    

但是它会让你写一些打破一些规则的东西。让我们学习这三种能力。

访问或更新 static mut

Rust 有一个称为‘static mut’的特性,它允许可变的全局状态。这样做会导致数据竞赛,因此本身是不安全的。有关详细信息,请参本书的静态部分。

解除引用一个原始指针

原始指针让你做任意指针的运算,会导致许多不同的内存安全问题。在某种意义上,一个任意指针的解除引用的能力是你可以做的最危险的事情。更多关于原始指针,请看本书相关部分。

调用 unsafe 函数

这最后的能力关于 unsafe 的两个方面:您只能调用一个 unsafe 块内标记 unsafe 的函数。    

这种能力是强大的。Rust 为 unsafe 函数提供一些编译器特性,绕过安全检查和一些安全功能,换来安全速度。    

我将再次重复:即使你可以在 unsafe 块和函数中做任意事情,并不意味着你应该这样做。虽然你坚持不变量编译器仍然将起作用,所以要小心!