原文链接: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
,会使用它)
- 0和1以外的
每当值一个值被赋值,被传递给函数/原始操作或从函数/原始操作被返回时,都会发生值“生成”。
如果引用/指针为空(null)或它指向的所有字节不是同一分配的一部分,则它是“悬垂”的(特别是它们都必须是某些分配的一部分)。它指向的字节范围由指针值和指针类型的大小确定。结果是,如果范围为空,则“悬垂”与“非空”相同。请注意,切片和字符串指向其整个范围,因此,长度元数据永远不要太大(尤其是,分配,切片和字符串不能大于isize::MAX
字节)非常重要。如果由于某种原因这太麻烦了,请考虑使用原始指针。
只有这些。Rust语言自身可以导致未定义行为的操作就只有这些。当然,非安全函数和trait可以声明自己专有的安全规范,要求开发者必须遵守以避免未定义行为。比如,allocator API声明回收一段未分配的内存是未定义行为。
但是,违背这些专有的规范通常也只是间接地触发上面列出的行为。另外,编译器内联函数也可能引入一些规则,一般是针对代码优化的假设条件。比如,Vec和Box使用的内联函数要求传入的指针永远不能为null。
Rust对于一些模糊的操作则通常比较宽容。Rust会认为下列操作是安全的:
- 死锁
- 竞争条件
- 内存泄漏
- 调用析构函数失败
- 整型值溢出
- 终止程序
- 删除生产数据库
当然,有以上行为的程序极有可能就是错误的。Rust提供了一系列的工具减少这种事情的发生,但是完全地杜绝它们其实是不现实的。