Rust 安全代码
安全与非安全代码对比 安全与非安全代码交互 非安全Rust能做什么 编写非安全代码数据布局
repr(Rust) 类型中的奇行种 可选的数据表达方式所有权
所有权 引用 别名 生命周期 生命周期的局限 省略生命周期 无界生命周期 高阶trait边界 子类型和变性 Drop检查 幽灵数据 分解借用类型转换
类型转换 强制类型转换 点操作符 显式类型转换 变形未初始化内存
未初始化内存 安全方式 Drop标志 非安全方式资源管理
基于所有权的资源管理 构造函数 析构函数 泄露展开
展开 异常安全性 污染并发
并发 竞争 Send和Sync 原子操作实现 Vec
实现 Vec 布局 内存分配 push和pop 回收资源 DeRef 插入和删除 IntoIter RawVec Drain 处理零尺寸类型 最终代码FFI
FFIRust 安全代码
安全与非安全代码对比 安全与非安全代码交互 非安全Rust能做什么 编写非安全代码数据布局
repr(Rust) 类型中的奇行种 可选的数据表达方式所有权
所有权 引用 别名 生命周期 生命周期的局限 省略生命周期 无界生命周期 高阶trait边界 子类型和变性 Drop检查 幽灵数据 分解借用类型转换
类型转换 强制类型转换 点操作符 显式类型转换 变形未初始化内存
未初始化内存 安全方式 Drop标志 非安全方式资源管理
基于所有权的资源管理 构造函数 析构函数 泄露展开
展开 异常安全性 污染并发
并发 竞争 Send和Sync 原子操作实现 Vec
实现 Vec 布局 内存分配 push和pop 回收资源 DeRef 插入和删除 IntoIter RawVec Drain 处理零尺寸类型 最终代码FFI
FFIRust 展开
Rust有一个分层的错误处理体系:
- 如果有些值可以为空,就用
Option - 如果发生了错误,而错误可以被正常处理,就用
Result - 如果发生了错误,但是没办法正常处理,就让线程panic
- 如果发生了更严重的问题,中止(abort)程序
Option和Result在大多数情况下都是默认的优先选择,因为API的用户可以根据自己的考虑将它们变为panic或中止。panic会导致线程停止正常的执行流程、展开栈(unwind stack)、调用析构函数,整个流程和函数返回时一样。
从1.0开始,Rust对Panic的处理显得有些混乱。在很早很早以前,Rust的设计非常接近Erlang。和Erlang一样,Rust由许多轻量级的任务(task)组成,当任务进入错误状态的时候,它们使用Panic停止自己。Panic和Java或者C++中的异常不同,它不能在任意时间点被捕获。Panic只能被任务的所有者捕获,而捕获后必须立即对它进行相应处理,否则任务会自己停止。
展开(unwinding)在这种场景下十分重要,因为如果任务的析构函数没有被调用的话,会导致内存和其他系统资源的泄露。由于任务有可能在正常运行过程中就挂掉,它对于需要长期运行的系统很不友好。
而在后来Rust的发展过程中,我们推崇尽可能少的抽象,所以上文的编程风格也就显得过时了。轻量级的任务被重量级的操作系统线程所取代。不过在1.0的稳定版本中,panic还是只能被父线程捕获。这意味着捕获一个panic需要唤醒一个系统线程!这和Rust的零开销抽象的设计哲学是完全相悖的。
有一个不稳定的API叫做catch_panic,它可以在不启动一个线程的情况下捕获panic。不过我们还是希望你谨慎地使用它。特别是现在Rust对展开的实现已经针对“不展开”的情况做了很多的优化。即使一个程序支持展开,只要它没有做展开的动作,在运行期就没有额外的开销。但同时,真的展开操作是比Java等其他语言的开销更大的。不要在正常运行的情况下让你的程序栈展开。只有当程序出错或遇到极端的问题时,你才应该使用Panic。
Rust的展开方式没有试图和其他任何一种语言的展开方式相兼容。所以,从其他语言展开Rust的栈,或者从Rust展开其他语言的栈,全都属于未定义行为。你必须在进入FFI调用之前捕获所有的Panic!你可以决定具体的实现方法,但不能什么都不做。否则的话,最好的情况是你的应用程序会崩溃。而最坏的情况是,你的程序不会崩溃,但会在彻底混乱的状态下持续运行。
下一章:Rust 异常安全性
虽然前面说过我们应该慎用展开,但是还是有许多的地方会Panic。如果你对None调用unwrap、使用超出范围的索引值、或者用0做除数,你的程序就要panic。在debug模式下,所有的计算操作在溢出的时候也都会pan ...
AI 中文社