基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

Rust中的可变借用(&mut T)和不可变借用(&T)区别

知识点图片

在 Rust 中,借用(Borrowing) 是指在不获取所有权(Ownership)的情况下访问数据。

&T(不可变借用)和 &mut T(可变借用)是 Rust 内存安全模型的核心,它们的根本区别在于 权限(读/写)排他性(Exclusivity)

以下是详细的对比和解释:


1. 核心区别总结

特性 &T (不可变借用) &mut T (可变借用)
官方术语 共享引用 (Shared Reference) 独占引用 (Exclusive Reference)
读权限 ✅ 可以读取 ✅ 可以读取
写权限 不可修改数据 可以修改数据
数量限制 同一作用域内可以有 无限多个 同一作用域内 只能有一个
共存规则 可以与其他 &T 共存 不能与其他任何引用(&T&mut T)共存

2. 详细解释

A. 不可变借用 (&T)

  • 类比:就像PPT演示。所有人(多个引用)都可以同时看屏幕,但没有任何人可以在上面乱涂乱画。
  • 规则:你可以创建任意数量的不可变引用。
  • 目的:允许数据被并行读取,因为单纯的读取操作不会导致数据竞争。
plaintext
fn main() {
    let x = 10;
    
    // 可以同时存在多个不可变借用
    let r1 = &x;
    let r2 = &x;
    
    println!("r1: {}, r2: {}", r1, r2); // 合法
    // *r1 = 20; // ❌ 编译错误:不能通过 &T 修改数据
}

B. 可变借用 (&mut T)

  • 类比:就像编辑文档。为了防止冲突,同一时间只能有一个人拥有编辑权限。当你在编辑时,其他人既不能编辑,也不能看(防止看到编辑到一半的中间状态)。
  • 规则:在特定作用域内,只能存在一个可变引用,且此时不能存在其他任何引用。
  • 目的:防止数据竞争(Data Races)。
plaintext
fn main() {
    let mut x = 10;
    
    // 只能有一个可变借用
    let r1 = &mut x;
    *r1 += 10; // 修改数据
    
    println!("r1: {}", r1); // 合法
}

3. Rust 的“借用规则” (The Borrow Checker Rules)

Rust 编译器通过 借用检查器 强制执行以下铁律:

在任意给定时间,你要么只能拥有一个可变引用,要么拥有任意数量的不可变引用。二者不能同时存在。

❌ 错误示例:同时存在可变和不可变借用

这是新手最常遇到的错误。

plaintext
fn main() {
    let mut s = String::from("hello");

    let r1 = &s;      // 不可变借用
    let r2 = &mut s;  // ❌ 错误!已经借用为不可变了,不能再借用为可变
    
    println!("{}, {}", r1, r2);
}

为什么禁止?
如果 r2 修改了 s 的内存(比如 push_str 导致扩容重新分配内存),那么 r1 指向的地址就会变成无效内存(悬垂指针),或者 r1 读到的数据是不一致的。

❌ 错误示例:多个可变借用

plaintext
fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s; // ❌ 错误!不能同时有两个可变借用
    
    // println!("{}", r1); // 如果这里使用了 r1,上面的 r2 定义就会报错
}

为什么禁止?
防止数据竞争(Data Race)。如果两个指针同时试图写入同一块内存,且没有同步机制,程序的行为将是未定义的。


4. 作用域与生命周期 (NLL)

Rust 的借用检查非常智能。从 Rust 2018 版本开始引入了 非词法生命周期 (Non-Lexical Lifetimes, NLL)。这意味着引用的有效范围不再是整个花括号 {},而是最后一次使用它的地方

plaintext
fn main() {
    let mut x = 10;

    let r1 = &x; 
    println!("{}", r1); 
    // r1 的生命周期在这里就结束了,因为它后面不再被使用了

    let r2 = &mut x; // ✅ 合法!因为 r1 已经不再活跃
    *r2 = 20;
    println!("{}", r2);
}

5. 总结

  • &T (Shared):你是观众。你可以看,别人也可以看,但谁都不能改。
  • &mut T (Exclusive):你是作者。只有你能看,只有你能改,其他人靠边站。

这种机制在编译阶段就消除了数据竞争,是 Rust 实现“无垃圾回收(GC)且内存安全”的关键。

00:00
00:00