所有权与借用
ownership、borrowing、slices、stack vs heap。
所有权与借用
这一课你将能做到
- 说清所有权三条规则,判断任意一段代码里值的所有权归属
- 用
&/&mut写借用代码,读懂并修复借用检查器报错 - 分清 String 与 &str 的使用场景,写出接收引用而非抢夺所有权的函数
为什么要学
所有权是 Rust 替代 GC 的核心机制:编译器靠它决定每块内存在哪里释放、引用是否安全。这也是 Java 转 Rust 的第一道坎——Java 里赋值是「共享引用」,随手传都行;Rust 里赋值默认是「所有权转移」,传出去自己就不能再用了。
掌握它的直接收益:读得懂编译器报错(E0382 是初学者出现频率最高的错误码),并且天然写出无内存泄漏、无悬垂指针、无数据竞争的代码。面试聊 Rust 绕不开它。
先建立图像
栈(连续、快、定长) 堆(灵活、慢、动态分配)
┌─────────────┐ ┌──────────────────┐
│ s: String │──ptr──────→│ "judger" (堆内存) │
│ (ptr,len,cap)│ │ 所有者:s │
└─────────────┘ └──────────────────┘
- 整型、布尔、浮点这类定长小值放栈上,赋值时直接按位拷贝(Copy 语义)
- String、Vec 这类动态数据放堆上,栈上只留一个「胖指针」;赋值默认转移所有权(move 语义),而不是拷贝堆数据
三条规则,全部推论都从这来:
- 每个值有唯一所有者(owner)
- 同一时刻只能有一个所有者
- 所有者离开作用域,值被释放(drop)
跟着做
步骤 1:亲眼看一次 move
cargo new own-demo
cd own-demo
fn main() {
let s1 = String::from("judger");
let s2 = s1; // 所有权从 s1 move 到 s2
// println!("{s1}"); // ← 取消注释:error[E0382] s1 已失效
println!("{s2}");
}
把注释解开跑一次,读编译器报错:value borrowed here after move。这就是 Rust 在阻止你访问一块「已经不属于你」的内存——Java 里这行代码能跑(两个引用指向同一对象),Rust 在编译期直接拒绝。
步骤 2:需要两份就 clone,纯数字走 Copy
fn main() {
let s1 = String::from("judger");
let s2 = s1.clone(); // 深拷贝堆数据,s1 依然有效
println!("{s1} / {s2}");
let x: i32 = 5;
let y = x; // i32 实现了 Copy trait,按位拷贝,x 仍可用
println!("{x} + {y}");
}
规律:小而定长的类型(整数、浮点、bool、char、由它们组成的元组)赋值即拷贝;持有堆内存的类型(String、Vec、HashMap)赋值即 move。
步骤 3:用借用代替转移
fn byte_len(s: &String) -> usize { // &String:借用,不夺走所有权
s.len()
}
fn main() {
let s = String::from("hello rust");
let n = byte_len(&s); // 传引用
println!("{s} 的字节长度是 {n}"); // s 依然可用
}
& 借用就像「看一眼但不带走」:函数用完还回来。对照 Java,这相当于把参数声明为只读视图,但编译器强制执行——被借用的值在借用期内不能被释放或修改。
步骤 4:借用规则——一山不容二虎
fn main() {
let mut s = String::from("data");
let r1 = &s;
let r2 = &s; // 多个不可变借用共存:读读不冲突
println!("{r1} {r2}");
let r3 = &mut s; // 不可变借用用完后,可变借用合法
r3.push('!');
println!("{r3}"); // 同一时刻:要么多个 &,要么一个 &mut
}