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++ 中经常遇到(在智能指针之前)。
借款
Rust 的启示是,当混合使用别名和可变性时,就会出现内存问题:也就是说,当一块内存可以通过多个路径访问并且它被突变(或移走)时留下了悬空指针.
因此,借用检查的核心原则是:可变性 XOR 别名。原则上它类似于读写锁。
这意味着 Rust 编译器跟踪aliasing信息,它使用生命周期注释 (those 'a
in &'a var
) 将引用的生命周期和它们引用的值连接在一起。
如果某人引用了某个值或对其进行了 INTO(例如,对某个字段的引用),则该值被借用struct
或集合的元素)。借用的值无法移动。
可变性(无别名)
您只能获得一个可变引用 (&mut T
) 随时变为给定值,并且没有不可变的引用成这个值可能同时存在;它保证您可以独占访问该内存片段,因此您可以安全地对其进行变异。
别名(无可变性)
您可以获得多个不可变的引用 (&T
) 随时变为给定值。但是,您不能通过这些引用 (*) 改变任何内容。
(*) 我在说谎;有像这样的结构RefCell
实现“内部可变性”;他们确实尊重可变性异或别名原则,但将检查推迟到运行时。
就是这样?
几乎如此;)
对于编译器编写者来说,实现起来已经相当复杂,并且可能会过度限制用户(一些安全的程序无法使用该系统证明安全,需要跳过障碍),但核心原理确实如此简单。
那么还剩下什么呢?
边界检查。这不是火箭科学,但可能会导致性能损失。大多数语言都对其有一定程度的支持,C 是一个大例外,而 C++ 有some支持它,尽管它是可选的。