借用与引用
学习目标
- 理解不可变引用
&T - 理解可变引用
&mut T - 掌握借用规则
- 理解为什么这些规则能防止数据竞争
核心概念
不可变引用(&T)
借用而不获取所有权:
fn calculate_length(s: &String) -> usize {
s.len()
} // s 离开作用域,但不 drop(因为它不拥有这个值)
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // 借用 s
println!("'{}' 的长度是 {}", s, len); // ✅ s 仍然有效
}
内存布局:
s (栈) 堆内存
┌──────────┐ ┌──────────┐
│ ptr ────────→ │ "hello" │
│ len: 5 │ └──────────┘
│ cap: 5 │
└──────────┘
↑
&s (引用) ──── 指向 s,不拥有堆内存
可变引用(&mut T)
fn push_world(s: &mut String) {
s.push_str(", world!");
}
fn main() {
let mut s = String::from("hello");
push_world(&mut s);
println!("{}", s); // "hello, world!"
}
借用规则(核心)
规则 1:任意时刻,只能有以下两种情况之一:
- 任意数量的不可变引用(&T)
- 或者恰好一个可变引用(&mut T)
- 不能同时存在
规则 2:引用必须始终有效(不能悬垂引用)
fn main() {
let mut s = String::from("hello");
// ✅ 多个不可变引用同时存在
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2);
// ✅ 不可变引用用完后,可以创建可变引用
// (r1, r2 的最后一次使用在上面那行 println!)
let r3 = &mut s;
r3.push_str("!");
println!("{}", r3);
}
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &mut s; // ❌ 不能同时有不可变和可变引用
println!("{}, {}", r1, r2);
}
为什么有这个规则?
防止数据竞争(data race):
// 假设没有借用规则,这段代码会导致问题:
let mut data = vec![1, 2, 3];
let first = &data[0]; // 不可变引用
data.push(4); // 可变操作:可能重新分配内存
println!("{}", first); // first 可能指向已释放的内存!
NLL(Non-Lexical Lifetimes)
引用的生命周期在最后一次使用时结束,而不是作用域结束:
fn main() {
let mut s = String::from("hello");
let r1 = &s; // 不可变引用开始
let r2 = &s; // 不可变引用开始
println!("{} {}", r1, r2); // r1, r2 最后一次使用 → 生命周期结束
let r3 = &mut s; // ✅ 此时可以创建可变引用
r3.push_str("!");
}
切片引用
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let s = String::from("hello world");
let word = first_word(&s);
println!("第一个单词: {}", word);
}
实践练习
练习 1:修复借用错误
// ❌ 这段代码有编译错误,修复它
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
let r3 = &mut s;
println!("{}, {}, {}", r1, r2, r3);
}
修复方案:
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2); // r1, r2 在此之后不再使用
let r3 = &mut s;
println!("{}", r3);
}
练习 2:用借用写函数
// 不获取所有权,计算字符串中某个字符的出现次数
fn count_char(s: &str, target: char) -> usize {
s.chars().filter(|&c| c == target).count()
}
fn main() {
let text = String::from("hello world");
let count = count_char(&text, 'l');
println!("'l' 出现了 {} 次", count);
println!("原始文本: {}", text); // text 仍然有效
}
练习 3:可变借用
fn add_exclamation(s: &mut String) {
s.push('!');
}
fn main() {
let mut greeting = String::from("hello");
add_exclamation(&mut greeting);
add_exclamation(&mut greeting);
println!("{}", greeting); // "hello!!"
}
常见错误
1. 同时持有可变和不可变引用
let mut s = String::from("hello");
let r1 = &s;
s.push_str("!"); // ❌ 不能在 r1 活跃时修改 s
println!("{}", r1);
2. 从函数返回局部变量的引用
// ❌ 悬垂引用
fn dangle() -> &String {
let s = String::from("hello");
&s // s 在函数结束时被 drop,引用指向无效内存
}
// ✅ 返回所有权
fn no_dangle() -> String {
String::from("hello")
}
3. 在持有引用时尝试 move
let mut s = String::from("hello");
let r = &s;
let s2 = s; // ❌ 不能在借用期间移动
println!("{}", r);
小结
| 符号 | 含义 |
|---|---|
&T | 不可变引用(只读借用) |
&mut T | 可变引用(读写借用) |
&s | 创建不可变引用 |
&mut s | 创建可变引用 |
| 规则 | 说明 |
|---|---|
多个 &T | 可以同时存在 |
&T + &mut T | 不能同时存在 |
&mut T + &mut T | 不能同时存在 |
| 引用必须有效 | 不能悬垂 |