Rust的安全性设计

Rust 是一门可以帮助我们编写高性能且高可用的程序的语言。Rust 的语言设计中包含了很多关于性能和可靠性的考虑,这次简单介绍一部分关于可靠性(安全性)的设计考虑。

Rust 语言

Rust 是一门强类型的编译型语言,专注于性能和可靠性方面。

Rust 可以用于开发命令行工具、WEB 服务、DevOps 工具、嵌入式设备、音视频分析与转码、数字货币、生物信息学、搜索引擎、物联网应用、机器学习。Firefox 浏览器大部分组成都是使用 Rust 开发的。

Rust 提供了一整套开发工具包:包管理工具 Cargo、代码格式化工具、用于 IDE 集成的 RLS(Rust Language Server)。

Rust 会比其他大多数语言增加更多的约束,通过编译器检查这些约束,来在前期保证一定的可靠性。

Rust 在语法和类型的设计上,更倾向于时刻提醒开发者关于可靠性的选择。

Rust 的很多语法的设计都是基于默认更加安全的方式来设计的,比如:变量默认不可变、引用默认不可变、模块内部默认不可见等。

类型安全

类型安全是所有强类型语言共有的特性,然而与大部分语言不同,Rust 中没有 null,你必须通过 Option<T> 类型来处理可能空值的情况。

1
2
3
4
5
// result可能是一个Result实例或者是null
Result result = Example.getResult();

// 如果 result 是 null,这一段会怎么样?
System.out.println("Got result: " + result.toString());
1
2
3
4
5
6
7
8
9
10
// get_result() 如果可能产生空的结果,那他必须返回 Option<Result>
let result = Example.get_result();

// 直接使用会产生编译错误
println!("Got result: {}", result);

// 必须要进一步判断才能使用
if let Some(result) = result {
println!("Got result: {}", result);
}

内存安全

说到内存管理就得说到垃圾回收,说到垃圾回收,除了开发者自己管理内存,常见的自动回收方式可能就是引用计数、标记擦除等方法,大部分都会产生 Stop-The-World 问题(针对这个问题,有些语言给出了优化的 GC 机制)。

  • 如果由开发者自己管理内存,那么开发者的水平和开发时的精神状态,将直接与程序的健壮性相关
  • 如果使用主流的 GC 机制,那么就无法避免 GC 中的多余开销(比如计数、标记,移动内存等)

内存管理从根本上来说是为了解决内存安全的问题:

  • 如果内存没有在使用却没有释放,将造成无用的内存占用,长时间运行容易造成内存占用越来越大,最终无法导致性能极度下降或者无法保持程序执行
  • 如果内存仍然在使用却提前释放,将导致多个数据使用同一片内存,将导致无法预期的错误或者是安全问题

Rust 通过增加更加严格的约束,从规则上面规避这些内存问题,并且由于这些约束是编译时检查,不会造成运行时多余的开销。

Rust 增加的规则被称为是所有权(Ownership),其规则为 3 条:

  1. 每个值都有一个所有者,通常是一个变量
  2. 每个值同时都只能有一个所有者
  3. 当所有者的生命周期结束时(变量的作用域结束时),所拥有的值会被释放(内存被回收)
1
2
3
fn some() {
let s = String::from("hello"); // 分配了新的堆空间
} // 变量s将不存在,其所拥有的堆空间将立即被释放

引用、数据竞争问题

通过引用的方式,可以在不影响所有权的情况下,使用其他所有者所持有的值,但是引用同样存在约束,这是为了避免「数据竞争」问题,对引用的约束是:

  1. 在任意的时间点,每个值只能拥有一个可变引用,或者多个不可变引用
  2. 当引用存在时,所有者不能先被释放(即不允许存在「悬挂引用」)

在引用的问题上,Rust 还提供了生命周期范型来解决更加复杂的引用问题(引用传递时的生命周期问题)。

线程安全

当程序存在多个线程时,会存在一些问题:

  • 竞争条件:多个线程尝试访问同一个数据,其顺序无法预期
  • 死锁:多个线程等待对方完成执行,导致这些线程无法继续执行
  • 只会在特定的执行顺序下产生的缺陷,难以稳定地重现和修复

Rust 中,线程无法使用别的线程中的数据,除非在创建线程时,将数据的所有权移交给这个线程(同时原本的线程将失去所有权)。

多个线程间可以通过消息通道来传递数据,或者通过信号量来协同多个线程的执行。

还有其他的方式来允许更加复杂的线程数据访问。

错误处理

Rust 语言中的错误被分为了两类:不可恢复错误(panic)与可恢复错误(用 Result 来表示结果)。

其中,不可恢复错误是指程序陷入了非预期的状态,通常出现在以非预期的参数来调用函数时。

而可恢复错误是指程序可以预见的问题,以 Result 来表示可能失败的结果,提示开发者需要处理错误情况。

# Rust
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×