Rust 借用与引用
共享引用 &T 与可变引用 &mut T 的借用规则:可同时存在多个只读借用,或唯一可变借用,但不能二者并存于同一时刻的重叠作用域。
#type / concept
#status / growing
#tech / dev
#resource / rust
[!info] 关联笔记
Rust 借用与引用
这个概念为什么出现
所有权解决“谁负责释放”,但工程里更常见的是:暂时使用别人的数据,而不夺走它。
- 只读查询函数不应吃掉
String - 修改缓冲区时需要独占写入
- 多处同时读可以,读的同时写不行
借用(borrowing)用引用类型表达这些意图,并在编译期检查。
[!abstract] 一句话理解
&T是共享只读借用,可同时存在多个;&mut T是独占可变借用,同一时刻只能有一个;二者在重叠作用域内不能同时存在——共享 xor 可变。
最小可运行示例
场景:订单备注——只读展示 vs 客服追加
工单系统里,展示页只读打印备注;客服工具要在同一条 String 后追加处理记录。
展示用 &str/&String 借用;追加用 &mut String。
// 业务意图:
// 1. 展示备注(不夺走所有权);
// 2. 客服追加一行处理痕迹;
// 3. 展示与追加不能在同一作用域“又读又独占写”。
// 教学点:
// - &T 可多处只读;
// - &mut T 独占;
// - 借用结束后所有者仍可用。
fn show_note(note: &str) {
// 只读借用:函数不拥有字符串
println!("note={}", note);
}
fn append_agent_mark(note: &mut String, agent: &str) {
// 可变借用:独占修改堆上内容
note.push_str(" | handled_by=");
note.push_str(agent);
}
fn main() {
let mut note = String::from("refund request");
// 多个共享借用可以同时存在
let a = note.as_str();
let b = note.as_str();
show_note(a);
show_note(b);
// a、b 的借用在这里结束后,才能再取 &mut
append_agent_mark(&mut note, "agent-7");
show_note(¬e);
}
建议运行:
cargo run
期望输出:
note=refund request
note=refund request
note=refund request | handled_by=agent-7
结合场景再看三个关注点
- 展示函数签名用
&str:调用方继续拥有数据,可多次展示。 - 追加必须
&mut:若展示借用仍存活时取&mut,编译失败——这是功能,不是噪音。 - 借用有作用域:NLL(非词法生命周期)让“用完即结束”的借用更符合直觉,但规则仍成立。
核心概念与准确模型
两种引用
| 类型 | 含义 | 数量约束 |
|---|---|---|
&T | 共享、不可变(就借用规则而言) | 可多个 |
&mut T | 独占、可变 | 同一时刻至多一个 |
规则浓缩
在同一数据上:
- 任意数量的
&T,或 - 唯一一个
&mut T
不能混用重叠。
解引用
let mut x = 5;
let r = &mut x;
*r += 1; // 通过引用写回
方法调用常自动引用/解引用(Deref 强制),入门先会显式 &/&mut。
悬垂引用被禁止
不能返回指向函数内局部变量的引用——所有者已 drop,引用会悬垂;编译器拒绝。
与可变绑定的关系
- 要创建
&mut T,绑定本身通常需mut mut说“绑定可写”;&mut说“这次借用是独占写”
设计动机
- 无数据竞争的安全基础(单线程别名可变已危险,多线程更甚)
- 不强制 clone 即可共享只读
- API 意图编码进类型:
&T/&mut T/T三者表达不同所有权诉求
边界情况与反直觉行为
- 重新借用:可变借用的“缩短”与编译器 MIR 分析使部分代码可通过,勿用“行号直觉”硬背,以编译器为准
- 内部可变性(
RefCell)在共享引用下提供运行时借用检查,见 内部可变性 &mut T可再借出&T在受限模式中(可重借用),细节进阶
常见误区
[!warning] 常见误区:把引用当成 GC 托管指针 错误理解:引用让值永远活着。
正确模型:引用不能比所有者活得更久。
为何易错:托管语言的引用语义迁移。
[!warning] 常见误区:同时 println 持有借用又 push 错误理解:打印只是读,不该影响写。
正确模型:若只读借用在活跃区间,则不能再&mut。
拆成先读完再写,或缩小借用作用域。
与相邻概念对比
| 所有权 | 借用 | |
|---|---|---|
| 是否转移主人 | move 会 | 否 |
| 典型签名 | fn f(s: String) | fn f(s: &str) / &mut String |
| 调用后源 | 可能失效 | 仍可用(规则满足时) |
工程实践
- 只读 API 优先
&str/&[T]/&T - 需要修改就
&mut T,并缩短借用区间 - 只有真正转移所有权时才收
T - 编译器 E0502 等错误按“谁在同时借用”排查
可验证实验
- 同时存在
&s与&mut s并使用 → 应失败 - 先
println!("{s}")结束借用,再s.push_str→ 应成功 - 写函数返回局部
String的&str→ 应失败
本节总结
- 借用让你使用而不拥有。
- 共享 xor 可变。
- 引用不能超过所有者寿命。
- 下一站:切片把“借用一段连续数据”语言化。
自测题
概念题
- 为什么不能同时有
&T与&mut T?
代码推理题
fn len(s: &String) -> usize { s.len() }与fn len(s: &str)哪个更通用?
工程思考题
- 公共库方法该收
String还是&str?
参考答案
- 避免别名可变导致的数据竞争与逻辑破坏。
&str更通用:可接受String、字面量等。- 只读看数据用
&str;需要拥有再存/改再返回时用String或返回新所有权。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| The Book — References and Borrowing | 官方书 | 借用规则 |
| Rust Reference — References | 规范向 | 引用类型 |