如何从内存中申请空间来存放程序的运行内容,如何在不需要的时候释放这些空间,以下的几种方式:
- 垃圾回收机制(GC),在程序运行时不断寻找不再使用的内存,典型代表:Java、Go。
- 手动管理内存的分配和释放, 在程序中,通过函数调用的方式来申请和释放内存,典型代表:C++。
- 通过所有权来管理内存,编译器在编译时会根据一系列规则进行检查。
其中 Rust 选择了第三种,最妙的是,这种检查只发生在编译期,因此对于程序运行期,不会有任何性能上的损失。Ownership和Borrowing也是rust特有的机制,Rust 用所有权避免内存泄漏和悬垂指针,不用垃圾回收。
以下尝试用最简单的方式来解释这2种机制。
✅ 一、所有权(Ownership)——“一个东西只能有一个人管”
规则:每个值有且仅有一个“主人”(变量),主人离开作用域,值就被销毁。赋值或传参时,所有权被转移(不是复制!)。
let s1 = String::from("hello");
let s2 = s1; // s1 把所有权给了 s2
println!("{}", s1); // ❌ 错误!s1 已经“失效”
总结:s1 被“move”了,不能再用。
✅ 二、借用(Borrowing)——“用一下,不拿走”
想用值但不拿走所有权?用引用(&)。
let s1 = String::from("hello");
let len = calc_length(&s1); // 借用 s1
println!("{}", s1); // ✅ OK!s1 还在
fn calc_length(s: &String) -> usize {
s.len() // 只是“读”,不拥有
}
总结: 🔑 &s1 是“借用”——你用,但不归你。
✅ 三、可变借用——“借走还能改”
默认借用是只读的。要修改,必须可变借用。
let mut s = String::from("hello");
change(&mut s); // 可变借用,注意:当使用 mut 关键字声明一个变量时,该变量就可以被修改
println!("{}", s); // 输出 "hello world"
fn change(s: &mut String) {
s.push_str(" world"); //追加
}
⚠️ 注意:同一时间,只能有一个可变借用,或多个不可变借用(不能又读又写)。
这种限制的好处就是使 Rust 在编译期就避免数据竞争,数据竞争可由以下行为造成:
- 两个或更多的指针同时访问同一数据
- 至少有一个指针被用来写入数据
- 没有同步数据访问的机制
fn main() {
let mut s = String::from("hello");
let r1 = &s; // ✅ 不可变借用
let r2 = &s; // ✅ 还可以再借一个(读-读允许)
let r3 = &mut s; // ❌ 错误!不能在 r1、r2 存在时借可变引用
println!("{}, {}, and {}", r1, r2, r3);
}
let mut s = String::from("hello");
let r1 = &mut s; // ✅ 可变借用
let r2 = &s; // 不行!只要有 &mut s,就不能再有 &s。
//很多时候,大括号可以帮我们解决一些编译不通过的问题,通过手动限制变量的作用域:
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 在这里离开了作用域,所以我们完全可以创建一个新的引用
let r2 = &mut s;
✅ 总结
所有权:值只有一个主人,主人走了,值就没了。
引用:&x 借来用,不拿走。
可变引用:&mut x 借来还能改,但只能一个。