从本质上讲,Rust 如何实现内存安全实际上非常简单。它主要取决于两个原则:所有权和借款。
所有权
编译器使用仿射类型系统来跟踪每个值的所有权:一个值最多只能使用一次,之后编译器将拒绝再次使用它。
fn main() {
let original = "Hello, World!".to_string();
let other = original;
println!("{}", original);
}
产生错误:
error[E0382]: use of moved value: `original`
--> src/main.rs:4:20
|
3 | let other = original;
| ----- value moved here
4 | println!("{}", original);
| ^^^^^^^^ value used here after move
|
= note: move occurs because `original` has type `std::string::String`, which does not implement the `Copy` trait
这尤其避免了在 C 或 C++ 中经常遇到的可怕的double-free(在智能指针之前)。
借款
来自 Rust 的启示是,当混叠别名和可变性混合时会出现内存问题:也就是说,当一块内存可以通过多个路径访问并且它被变异(或移走)留下 悬空指针.
因此,借用检查的核心原则是:Mutability XOR Aliasing。原则上,它类似于读写锁。
这意味着 Rust 编译器跟踪 aliasing 信息,为此它使用 生命周期注释(&'a var 中的 'a)连接引用的生命周期以及它们一起引用的值。
如果有人对某个值有引用或 INTO 引用,则该值被借用(例如,对 struct 的字段的引用或对集合的元素的引用)。借来的值不能移动。
可变性(无别名)
您在任何时候都只能获得单个可变引用 (&mut T) 到给定值,并且不能同时存在到该值的不可变引用时间;它保证您可以独占访问这些内存,因此您可以安全地对其进行变异。
别名(无可变性)
您可以随时将多个不可变引用 (&T) 获取到给定值中。但是,您不能通过这些引用 (*) 改变任何内容。
(*) 我在撒谎;有像RefCell 这样的结构实现“内部可变性”;他们确实尊重 Mutability XOR Aliasing 原则,但将检查推迟到运行时。
就这样?
差不多;)
对于编译器编写者来说实现起来已经相当复杂了,并且可能会过度限制用户(一些安全的程序使用这个系统无法证明是安全的,需要跳过箍),但是核心原则确实就这么简单。
那么还剩下什么?
边界检查。这不是火箭科学,但可能会导致性能损失。大多数语言对它都有一定程度的支持,C 是最大的例外,C++ 对它有一些支持,尽管它是可选的。