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(&note);
}

建议运行:

cargo run

期望输出:

note=refund request
note=refund request
note=refund request | handled_by=agent-7

结合场景再看三个关注点

  1. 展示函数签名用 &str:调用方继续拥有数据,可多次展示。
  2. 追加必须 &mut:若展示借用仍存活时取 &mut,编译失败——这是功能,不是噪音。
  3. 借用有作用域:NLL(非词法生命周期)让“用完即结束”的借用更符合直觉,但规则仍成立。

核心概念与准确模型

两种引用

类型含义数量约束
&T共享、不可变(就借用规则而言)可多个
&mut T独占、可变同一时刻至多一个

规则浓缩

在同一数据上:

  1. 任意数量的 &T
  2. 唯一一个 &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
调用后源可能失效仍可用(规则满足时)

工程实践

  1. 只读 API 优先 &str / &[T] / &T
  2. 需要修改就 &mut T,并缩短借用区间
  3. 只有真正转移所有权时才收 T
  4. 编译器 E0502 等错误按“谁在同时借用”排查

可验证实验

  1. 同时存在 &s&mut s 并使用 → 应失败
  2. println!("{s}") 结束借用,再 s.push_str → 应成功
  3. 写函数返回局部 String&str → 应失败

本节总结

  • 借用让你使用而不拥有。
  • 共享 xor 可变。
  • 引用不能超过所有者寿命。
  • 下一站:切片把“借用一段连续数据”语言化。

自测题

概念题

  1. 为什么不能同时有 &T&mut T

代码推理题

  1. fn len(s: &String) -> usize { s.len() }fn len(s: &str) 哪个更通用?

工程思考题

  1. 公共库方法该收 String 还是 &str
参考答案
  1. 避免别名可变导致的数据竞争与逻辑破坏。
  2. &str 更通用:可接受 String、字面量等。
  3. 只读看数据用 &str;需要拥有再存/改再返回时用 String 或返回新所有权。

延伸阅读与资料来源

资料类型支撑内容
The Book — References and Borrowing官方书借用规则
Rust Reference — References规范向引用类型
创建于 2026/7/15 更新于 2026/7/15