Rust 是一门可以帮助我们编写高性能且高可用的程序的语言。Rust 的语言设计中包含了很多关于性能和可靠性的考虑,这次简单介绍一部分关于可靠性(安全性)的设计考虑。
Rust 语言
Rust 是一门强类型的编译型语言,专注于性能和可靠性方面。
Rust 可以用于开发命令行工具、WEB 服务、DevOps 工具、嵌入式设备、音视频分析与转码、数字货币、生物信息学、搜索引擎、物联网应用、机器学习。Firefox 浏览器大部分组成都是使用 Rust 开发的。
Rust 提供了一整套开发工具包:包管理工具 Cargo、代码格式化工具、用于 IDE 集成的 RLS(Rust Language Server)。
Rust 会比其他大多数语言增加更多的约束,通过编译器检查这些约束,来在前期保证一定的可靠性。
Rust 在语法和类型的设计上,更倾向于时刻提醒开发者关于可靠性的选择。
Rust 的很多语法的设计都是基于默认更加安全的方式来设计的,比如:变量默认不可变、引用默认不可变、模块内部默认不可见等。
类型安全
类型安全是所有强类型语言共有的特性,然而与大部分语言不同,Rust 中没有 null
,你必须通过 Option<T>
类型来处理可能空值的情况。
1 | // result可能是一个Result实例或者是null |
1 | // get_result() 如果可能产生空的结果,那他必须返回 Option<Result> |
内存安全
说到内存管理就得说到垃圾回收,说到垃圾回收,除了开发者自己管理内存,常见的自动回收方式可能就是引用计数、标记擦除等方法,大部分都会产生 Stop-The-World 问题(针对这个问题,有些语言给出了优化的 GC 机制)。
- 如果由开发者自己管理内存,那么开发者的水平和开发时的精神状态,将直接与程序的健壮性相关
- 如果使用主流的 GC 机制,那么就无法避免 GC 中的多余开销(比如计数、标记,移动内存等)
内存管理从根本上来说是为了解决内存安全的问题:
- 如果内存没有在使用却没有释放,将造成无用的内存占用,长时间运行容易造成内存占用越来越大,最终无法导致性能极度下降或者无法保持程序执行
- 如果内存仍然在使用却提前释放,将导致多个数据使用同一片内存,将导致无法预期的错误或者是安全问题
Rust 通过增加更加严格的约束,从规则上面规避这些内存问题,并且由于这些约束是编译时检查,不会造成运行时多余的开销。
Rust 增加的规则被称为是所有权(Ownership),其规则为 3 条:
- 每个值都有一个所有者,通常是一个变量
- 每个值同时都只能有一个所有者
- 当所有者的生命周期结束时(变量的作用域结束时),所拥有的值会被释放(内存被回收)
1 | fn some() { |
引用、数据竞争问题
通过引用的方式,可以在不影响所有权的情况下,使用其他所有者所持有的值,但是引用同样存在约束,这是为了避免「数据竞争」问题,对引用的约束是:
- 在任意的时间点,每个值只能拥有一个可变引用,或者多个不可变引用
- 当引用存在时,所有者不能先被释放(即不允许存在「悬挂引用」)
在引用的问题上,Rust 还提供了生命周期范型来解决更加复杂的引用问题(引用传递时的生命周期问题)。
线程安全
当程序存在多个线程时,会存在一些问题:
- 竞争条件:多个线程尝试访问同一个数据,其顺序无法预期
- 死锁:多个线程等待对方完成执行,导致这些线程无法继续执行
- 只会在特定的执行顺序下产生的缺陷,难以稳定地重现和修复
Rust 中,线程无法使用别的线程中的数据,除非在创建线程时,将数据的所有权移交给这个线程(同时原本的线程将失去所有权)。
多个线程间可以通过消息通道来传递数据,或者通过信号量来协同多个线程的执行。
还有其他的方式来允许更加复杂的线程数据访问。
错误处理
Rust 语言中的错误被分为了两类:不可恢复错误(panic)与可恢复错误(用 Result
来表示结果)。
其中,不可恢复错误是指程序陷入了非预期的状态,通常出现在以非预期的参数来调用函数时。
而可恢复错误是指程序可以预见的问题,以 Result
来表示可能失败的结果,提示开发者需要处理错误情况。