所有权、借用和生命周期 beginner 30 分钟 更新 2026-06-15

借用与引用

用 & 和 &mut 借用数据,掌握借用检查器的规则。

借用与引用

你会学到什么

  • & 创建共享借用 &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),借用在最后一次使用后即结束,所以下面 这段代码是合法的——共享借用 firstpush 之前已经用完:

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 之后才用,借用没结束

修复方式是缩小借用范围,让 firstpush 之前用完。

返回局部变量的引用——函数结束后局部变量被释放,引用会悬垂:

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