Rust引用与借用一

Rust 的安全感来自一条“死规矩”:谁申请的内存、谁负责把它收回。理解这条规矩离不开两个舞台——。下面用三段小代码,但把重点放在“钥匙怎么传、数据在哪里”。


先掰清:栈与堆在 Rust 里的分工

  • 栈(stack)
    • 存放固定大小、生命周期清晰的东西:例如整数、布尔值、指针本身。
    • 后进先出,函数退出时整块弹出,开销只有修改栈顶指针。
  • 堆(heap)
    • 存放大小不定或运行期才确定的数据:String 的字节数组、Vec 的元素、Box<T> 包的值。
    • 申请与释放都要走系统分配器(malloc/free)。在 Rust 里,释放时间靠所有权规则静态推断,而不是 GC。

可以这么记:栈放“门牌”,堆放“房子”;拿到门牌才能找到房子。


例子一:String——“搬家”与“借住”有本质区别

fn suy_uj() {
    let m1 = String::from("hello");   // 栈:3 元组门牌 → 堆:hello
    let m2 = String::from("world");

    // greeat(m1, m2);               // ❌ 把门牌 *移交*,自己失去一切权利
    greeat_y(&m1, &m2);              // ✅ 只“借钥匙”,权利仍在自己手里
    println!("还能用 m1,m2: {} {}", m1, m2);
}

为什么直接传 m1m2 会出错?

  1. 移动 (move):调用 greeat(m1, m2) 就是“连门牌带房子管家全一起搬走”。
  2. 函数返回前g1g2 会销毁房子;回到 suy_uj 时原地址已空。
  3. 编译器提前阻止:避免出现悬垂指针。

为什么加 & 就安全?

  • 引用 (reference) = 临时钥匙,作用域结束必须归还。
  • 真实房东依旧是 m1m2 —— 他们的所有权没动,只有“使用权”临时外放。
  • 借用期间编译器把房子冻结成“只读”,结束后解冻。

一句口诀
传值 = 把钥匙交出去;传 &值 = 借钥匙但保留房契


例子二:Box<T>——唯一房东 vs. 游客卡

fn kj() {
    let x = Box::new(1);       // x 是唯一房东(门牌)→ 堆: 1
    let y = x;                 // 房契过户给 y,x 失效

    let r1 = &y;               // 游客卡,只能看
    let r2 = &y;               // 再来一张
    // y 本身依旧有改房功能(可写)——只要游客卡不存在 &mut 冲突
}

Box 为什么只允许一个房东?

  • 设计目标:简洁、无计数开销;谁拿着门牌谁负责拆房。
  • 一旦移动 (x → y):旧房东失效,防止“两个人都说房子是我的”。

引用在 Box 世界里的角色

  • &y 就是“围观票”:看到的是放在栈上的门牌数字,不能改。
  • 多张只读票一起 合法,因为没人动房子。
  • 如果想“多人一起翻修”——
    • Rc<T>:单线程引用计数,后端数人头;
    • Arc<T>:多线程安全计数。

一句口诀
Box 门牌只能过户,不能复印;引用是复印件,但只能看不能改

快速对比:引用与所有权的关系

场景有无所有权读/写权限何时释放堆内存
m1 传值✅ 转移读+写新房东作用域结束
&m1❌ 仅借用默认只读*原房东作用域结束
Box 移动✅ 转移读+写新房东作用域结束
&Box<T>❌ 仅借用只读原房东作用域结束

*若借用方要写,必须借 &mut,此时编译器保证没有任何其他引用存在。

引用 = “临时钥匙”,借完归还,不动房契。移动 = “过户”,以后谁持有谁拆房,旧房东立刻失效。


例子三——逐帧“透视”借用检查器

pub fn nj() {
    let x = 0;                  // ──①
    let mut x_ref = &x;         // ──②
    println!("{}", x_ref);      // ──③

    let y = 1;                  // ──④
    x_ref = &y;                 // ──⑤
}

下面把代码按 ①-⑤ 五个瞬间“定格”,看看 Borrow Checker(借用检查器)如何分配 读 / 写 权限。


let x = 0;

  • 谁拥有?
    x 是栈上的一个 i32,变量 x 本身就是“房东”。
  • 可读 / 可写?
    由于 x 没有被借出,房东既可读也可写。
  • 借用检查器记录 状态:无借用 — 随时可以拿出 &x&mut x,但不能两种同时存在。

let mut x_ref = &x;

  • 发生了什么?
    创建了一个不可变借用,变量名 x_ref 本身可重新绑定(因为 mut 修饰的是引用变量)。
  • 权限分配
    • x_ref(&x) → 只读
    • x → 冻结为只读(房东暂时不能写)
  • 借用检查器记录 状态:存在 1 个只读借用 — 禁止任何 &mut x,但允许更多 &x

println!("{}", x_ref);

  • 动作
    读取 x_ref,间接读取 x。这只触发只读访问,符合当前权限。
  • 借用检查器更新
    状态不变:只读借用仍在,房东仍不能写。

let y = 1;

  • 与借用无关
    y 是全新的变量,与 x 原先的借用关系无冲突。
  • 权限现状
    • x → 依旧“只读”冻结
    • y → 房东可读可写,无借用
  • 借用检查器记录
    y 作为另一套记录,互不影响。

x_ref = &y;

  • 关键操作
    我们不是修改 &x 指向的数据,而是让 x_ref 这根“绳子”改绑到新房子 y
  • 权限重新洗牌
    1. 借用 &x 结束:房东 x 立刻解冻,恢复可写可读。
    2. 新借用 &y 产生:y 进入只读借用状态,x_ref 只能读新地址。
  • 借用检查器检查点
    它确认旧借用生命周期在这里确实停止——因为之后再也没用到 &x。如果我们稍后又使用 x_ref 并假定它还是指向 x,编译器就会报错。

借用检查器到底做了什么?

  • 静态分析
    它在编译阶段为每个借用打上“开始-结束”标记,保证不同借用的读写权限在时间线上不冲突。
  • 核心规则
    1. 同时可以有零个或多个只读借用 (&T)。
    2. 同一时间至多一个独占可写借用 (&mut T),且它与任何只读借用互斥。
  • 结果
    运行期无需锁,也没有 GC,却仍然避免数据竞争、空悬指针和双重释放。

小结 —— 读写权限一览

瞬间x 权限x_ref 权限y 权限
读+写
②③
读+写
读+写读 (&y)

一句话记住:引用变量可“改绑”并不代表能“改房子”;真正想改房子,就得拿到那把唯一的可写钥匙 &mut T

消息盒子
# 您需要首次评论以获取消息 #
# 您需要首次评论以获取消息 #

只显示最新10条未读和已读信息