认识安全与不安全

安全的“高级”语言程序猿面临着一个根本的困境。一方面,只关心你想要的而不取考虑它是如何做到的是非常棒的。另一方面,这会导致糟糕的不可接受的性能。为了获得你想要的性能表现而放弃简明和习惯的实践可能是必须的。或者你可以仅仅在厌恶中放弃抵抗并交付一个没有那么多给力语法糖的不安全语言的实现。

更糟的是,当你想要直面操作系统时,你不得不面对一个不安全语言:C语言。C语言始终存在并不可避免。它是编程世界的通用语言(lingua-franca)。即便其他安全语言也基本会为了大千世界(for the world at large)而暴露C语言接口!不管你是为什么这么做,一旦你的程序涉及到C语言它不再安全了。

即便如此,Rust是一个完全安全的编程语言。

好吧,Rust一个安全编程语言(子集?)。让我们退一步说。

Rust可以被认为由两个编程语言组成:安全Rust不安全Rust。安全Rust是为了真正完全的安全。不安全Rust,不出意外的,并不是真正完全的安全。事实上,不安全Rust允许你做一些真正不安全到疯狂的事。

安全Rust是纯粹的Rust编程语言。如果所有你需要做的就是编写Rust,你永远也不需要去担心类型安全和内存安全。你将永远也不需忍受一个空的或悬垂的指针,或者任何那些没道理的未定义行为(Undefined Behaviour)。

这真是叼炸天(awesome)。

标准库也提供了足够的让你能够写出超高性能应用的符合语言习惯的纯粹的安全Rust编写的开箱即用的功能。

不过也许你想调用另一个语言。也许你想想编写一个标准库并未暴露的底层抽象。也许你正在编写标准库(它完全由Rust编写)。也许你需要做一些类型系统并不理解的行为,或者仅仅想来回移动一些该死的位。也许,你需要不安全Rust。

不安全Rust就像安全Rust一样,有着相同的规则和语义。然而不安全让你可以做一些额外的明显不安全的事。

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

  • 解引用裸指针
  • 调用unsafe函数(包括C语言函数,固有功能(intrinsics),和原始的allocator)
  • 实现unsafetrait
  • 改变静态量

就这些。这些操作被归类为不安全的原因是滥用其中任何一个都将会导致可怕的未定义行为。调用未定义行为给了编译器肆意破坏你程序的权利。很明显你应该调用未定义行为。

不像C语言,在Rust范围内未定义行为是非常有限的。语言核心所关注的全部是避免以下情况:

  • 解引用空或悬垂指针
  • 读取未初始化内存
  • 破坏指针别名规则
  • 产生无效的原始类型值:
    • 悬垂/空引用
    • 一个未定义的enum判别式
    • 一个不在[0x0, 0xD7FF][0xE000, 0x10FFFF]范围内的char
    • 一个非utf8编码的str
    • 在另一个语言中使用(Unwinding into another language)
    • 导致一个数据竞争

就这么多。这是所有Rust中未定义行为的成因。当然,不安全函数和trait也能声明一下额其它的限制, 一个程序必须维护这些限制来避免未定义行为。然而,通常违反这些限制将仅仅导致上述问题中的一个。一些额外的限制也可能来自编译器固有功能(compiler intrinsics),它们对代码如何进行优化做出了特殊的假设。

除此之外,Rust尊重并放任其它的不可靠的操作。Rust认为这些是“安全的”:

然而任何能做到这样一件事的程序可能是不正确的。Rust提供大量的工具来根除这些情况,不过避免这些问题被认为是明显不切实际的(又黑。。。)。