Rust 学习笔记 06:引用与借用 (References & Borrowing) 下

“Borrow checkers are the strict librarians of the programming world.”

上一集我们讲了所有权 (Ownership):把东西给别人(Move),自己就没了。这虽然安全,但太麻烦了。

比如我想计算一个字符串长度,难道每次都要把所有权传进去,算完再传回来?

1// 痛苦的写法
2fn calculate_length(s: String) -> (String, usize) {
3    let length = s.len();
4    (s, length) // 还要把 s 传回去,心累
5}

Rust 说:别急,你可以 借 (Borrow) 啊。

1. 引用 (References)

引用允许你使用值但不获取其所有权。在 Rust 中,用 & 符号表示。

 1fn main() {
 2    let s1 = String::from("hello");
 3
 4    // 传引用:&s1
 5    let len = calculate_length(&s1);
 6
 7    // s1 依然有效!因为我们只是借出去了,没送出去。
 8    println!("The length of '{}' is {}.", s1, len);
 9}
10
11// 接收引用:&String
12fn calculate_length(s: &String) -> usize {
13    s.len()
14}

这叫 借用 (Borrowing)。就像你借了朋友的书,你看完了得还回去(离开作用域),但书还是朋友的。

2. 可变引用 (Mutable References)

借来的书能乱涂乱画吗?默认不行(引用默认不可变)。 如果非要改,得申请 可变引用 (&mut)

 1fn main() {
 2    let mut s = String::from("hello");
 3
 4    change(&mut s);
 5    println!("{}", s); // hello, world
 6}
 7
 8fn change(some_string: &mut String) {
 9    some_string.push_str(", world");
10}

3. 借用规则 (The Rules)

这是 Rust 萌新最容易撞墙的地方。借用规则非常严格,为了防止 数据竞争 (Data Race)

规则一:一山不容二虎(可变引用) 在特定作用域内,对某一块数据,只能有 一个 可变引用。

1let mut s = String::from("hello");
2let r1 = &mut s;
3let r2 = &mut s; // 报错!
4// 理由:r1 正在改 s,r2 也要改 s,容易打架。

规则二:读写互斥 你不能在拥有不可变引用(读锁)的同时,拥有可变引用(写锁)。

1let mut s = String::from("hello");
2let r1 = &s; // 我在读
3let r2 = &s; // 我也在读,没问题
4let r3 = &mut s; // 报错!
5// 理由:r1 和 r2 以为数据不会变,结果 r3 偷偷改了,r1/r2 就读到脏数据了。

总结为一句话: 要么有很多个只读的(&T),要么只能有一个可写的(&mut T)。

4. 悬垂引用 (Dangling References)

在 C++ 中,你可能会不小心返回一个栈上局部变量的指针,导致指针指向无效内存。 在 Rust 中,编译器会直接教你做人。

1fn dangle() -> &String {
2    let s = String::from("hello");
3    &s // 报错:s 离开作用域就释放了,你返回个寂寞?
4}

解决方法:直接返回 String(转移所有权),而不是引用。

5. 小结

第六篇笔记,我们学会了如何"借书"。

  • & 是借(引用),&mut 是不仅借还要改(可变引用)。
  • 规则:多读 或 一写,绝不妥协。

这两章(Ownership + Borrowing)是 Rust 最难也是最精彩的部分。跨过这道坎,剩下的就是星辰大海。

下一篇,我们将介绍一种特殊的引用 —— Slice (切片)。它不仅是不持有所有权的引用,而且引用的是一段连续的内存序列。


练习题

  1. 尝试编写一段代码,违反"读写互斥"规则,看看编译器具体的报错信息。
  2. 思考:为什么 Rust 允许同时存在多个不可变引用?这会有线程安全问题吗?

思考题

NLL (Non-Lexical Lifetimes) 是 Rust 2018 引入的特性。它如何让借用检查变得更聪明?(提示:观察文中的代码注释 r1, r2 作用域在这里结束)。


本文代码示例

关注公众号:极客老墨

更多 AI 应用开发、工程实践和效率工具分享,欢迎扫码关注。

极客老墨微信公众号二维码

相关阅读