借用与引用
你会学到什么
- 用
&创建共享借用&T,用&mut创建可变借用&mut T。 - 借用规则:同一时刻要么多个共享借用,要么一个可变借用,不能同时。
- 借用不转移所有权,调用结束后数据仍归调用方。
- 非词法生命周期(NLL):借用在最后一次使用后就结束。
- 返回引用、
&str与&String的区别,以及方法的&self/&mut self。
最小示例
let mut scores = vec![10, 20, 30];
double_all(&mut scores); // 可变借用,原地修改
println!("{scores:?}"); // scores 仍然属于这里
运行代码
cd examples
cargo run -p rt_07_borrowing
cargo test -p rt_07_borrowing
代码讲解
完整源码已在本页下方嵌入,这里只点出几个关键片段。
共享借用 &T 是只读的,可以同时存在多个。参数写成切片 &[i32]
而不是 &Vec<i32>,这样数组、Vec、切片都能传进来:
fn sum(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
可变借用 &mut T 允许修改,但同一时刻只能有一个。写入被借用的值
需要用 * 解引用:
fn double_all(numbers: &mut [i32]) {
for n in numbers.iter_mut() {
*n *= 2;
}
}
返回引用 时,返回值指向传入的数据,因此不会悬垂。入参用 &str
而非 &String:&String 会自动解引用强制转换成 &str,所以 &str
既能接受字符串字面量,也能接受 String 的借用:
fn first_word(text: &str) -> &str {
text.split_whitespace().next().unwrap_or("")
}
方法的 &self / &mut self 其实就是对 self 的借用。&self
是只读借用、可多次调用;&mut self 是可变借用、同一时刻唯一:
impl Account {
fn balance(&self) -> i64 { self.balance } // 只读
fn deposit(&mut self, amount: i64) { self.balance += amount; } // 可变
}
得益于 非词法生命周期(NLL),借用在最后一次使用后即结束,所以下面
这段代码是合法的——共享借用 first 在 push 之前已经用完:
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("{first}"); // first 到此用完
v.push(4); // 可变借用,OK
借用检查器在编译期保证:不会出现“一边读一边写”的数据竞争。
常见错误
共享借用还活着时又要可变借用——把上面合法的例子改一下顺序就会报错:
let mut v = vec![1, 2, 3];
let first = &v[0]; // 共享借用
v.push(4); // ❌ 可变借用,与仍在使用的 first 冲突
println!("{first}"); // first 在 push 之后才用,借用没结束
修复方式是缩小借用范围,让 first 在 push 之前用完。
返回局部变量的引用——函数结束后局部变量被释放,引用会悬垂:
fn dangle() -> &String {
let s = String::from("oops");
&s // ❌ s 在函数结束时被丢弃
}
应当直接返回 String(移交所有权),或返回指向入参的引用。
练习
- 写一个函数
cap(words: &mut Vec<String>),把每个字符串末尾加上!。 - 写一个函数
max_ref(numbers: &[i32]) -> Option<&i32>,返回切片中最大值的引用。 - 给
Account增加一个withdraw(&mut self, amount: i64)方法,并补一个测试。
小结
借用让你在不转移所有权的前提下访问数据。共享借用 &T 可读不可写、可多个;
可变借用 &mut T 唯一且可写。借用在最后一次使用后结束(NLL)。方法上的
&self / &mut self 就是对自身的共享 / 可变借用。
下一步
切片是一种特别有用的借用形式,下一章详细学习。
完整示例代码
下面是 examples/07_borrowing/ 的完整源码。无需 clone 仓库,直接在页面上阅读、复制、对照运行。
examples/07_borrowing/src/main.rs
//! 引用与借用:共享借用 `&T` 与可变借用 `&mut T`。
//!
//! 借用让你在 **不转移所有权** 的前提下访问数据。核心规则只有一条:
//! 在任意时刻,要么存在 **任意多个共享借用 `&T`**,要么存在
//! **恰好一个可变借用 `&mut T`**,二者不能同时存在。借用检查器在
//! 编译期强制这条规则,从而在不需要垃圾回收的情况下杜绝数据竞争。
/// 共享借用:只读访问,不获取所有权。
///
/// 参数写成 `&[i32]`(切片)而不是 `&Vec<i32>`,这样数组、`Vec`、
/// 其它切片都能直接传进来,更通用。
fn sum(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
/// 可变借用:原地修改调用方的数据,调用结束后所有权仍归调用方。
fn double_all(numbers: &mut [i32]) {
for n in numbers.iter_mut() {
*n *= 2; // 解引用 `*n` 才能写入被借用的值
}
}
/// 返回借用:返回的引用指向 **传入的** 数据,因此不会悬垂。
///
/// 入参用 `&str` 而非 `&String`:`&String` 会自动“解引用强制转换”
/// 成 `&str`,所以 `&str` 能同时接受字符串字面量和 `String` 的借用。
fn first_word(text: &str) -> &str {
text.split_whitespace().next().unwrap_or("")
}
/// 一个小账户类型,用来演示 `&self`(只读方法)与 `&mut self`(可变方法)。
struct Account {
balance: i64,
}
impl Account {
fn new(balance: i64) -> Self {
Self { balance }
}
/// `&self`:只读借用自身,可以被多次、并发地调用。
fn balance(&self) -> i64 {
self.balance
}
/// `&mut self`:可变借用自身,同一时刻只能有一个这样的借用。
fn deposit(&mut self, amount: i64) {
self.balance += amount;
}
}
fn main() {
// --- 共享借用:可以同时存在多个 ---
let data = vec![1, 2, 3, 4];
let a = &data;
let b = &data; // 多个共享借用并存,没有问题
println!("[共享借用] a.len={}, b.sum={}", a.len(), sum(b));
println!("[共享借用] 原 data 仍可用 = {data:?}");
// --- 可变借用:原地修改 ---
let mut scores = vec![10, 20, 30];
double_all(&mut scores);
println!("[可变借用] 翻倍后 = {scores:?}");
// --- 非词法生命周期(NLL):借用在最后一次使用后即结束 ---
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("[借用范围] first = {first}"); // first 用完了
v.push(4); // 此处再可变借用 v 是允许的,因为上面的共享借用已结束
println!("[借用范围] push 之后 = {v:?}");
// --- 返回借用 & &str/&String ---
let owned = String::from("the quick fox");
println!("[返回借用] first_word = {}", first_word(&owned));
println!("[返回借用] 字面量也行 = {}", first_word("hello world"));
// --- &self vs &mut self ---
let mut account = Account::new(100);
println!("[方法] 初始余额 = {}", account.balance());
account.deposit(50); // 需要 &mut self
println!("[方法] 存款后余额 = {}", account.balance());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shared_borrow_reads() {
let data = vec![1, 2, 3, 4];
assert_eq!(sum(&data), 10);
// 借用之后原数据仍然可用。
assert_eq!(data.len(), 4);
}
#[test]
fn mutable_borrow_writes() {
let mut numbers = vec![1, 2, 3];
double_all(&mut numbers);
assert_eq!(numbers, vec![2, 4, 6]);
}
#[test]
fn first_word_handles_str_and_string() {
let owned = String::from("hello world");
// &String 自动转成 &str。
assert_eq!(first_word(&owned), "hello");
assert_eq!(first_word("rust ocean"), "rust");
assert_eq!(first_word(""), "");
}
#[test]
fn account_self_methods() {
let mut account = Account::new(100);
assert_eq!(account.balance(), 100); // &self
account.deposit(50); // &mut self
assert_eq!(account.balance(), 150);
}
} examples/07_borrowing/Cargo.toml
[package]
name = "rt_07_borrowing"
version.workspace = true
edition.workspace = true
publish.workspace = true