bigywater.tech
Rust · 19

不可变借用与可变借用

不可变借用与可变借用是所有权之后的第二块基石。这篇用真实的编译器报错把规则列清楚:能写什么、什么时候冲突、借用到哪里结束,以及一个容易被忽略的细节 —— &mut 传给函数时的重借用。

E0502 / E0499
读写冲突 / 两个可变借用的错误码
最后一次使用
借用在最后一次使用处结束,不是块结束
reborrow
把 &mut 传给函数时自动重借用,r 之后仍可用

& 与 &mut

实测 · 本机执行输出
  源码:
      1 | fn add_one(n: &mut i32) { *n += 1; }
      2 | fn read(n: &i32) -> i32 { *n * 2 }
      3 | fn main() {
      4 |     let mut x = 10;
      5 |     println!("read(&x) = {}", read(&x));
      6 |     add_one(&mut x);
      7 |     println!("add_one 之后 x = {x}");
      8 | }
  编译:成功
  运行(退出码 0):
    read(&x) = 20
    add_one 之后 x = 11
  源码:
      1 | fn bad(n: &i32) { *n += 1; }
      2 | fn main() { let mut x = 1; bad(&x); }
  编译:失败
    error[E0594]: cannot assign to `*n`, which is behind a `&` reference
     --> <实验目录>/src/u19b.rs:1:19
      |
    1 | fn bad(n: &i32) { *n += 1; }
      |                   ^^^^^^^ `n` is a `&` reference, so it cannot be written to
      |
    help: consider changing this to be a mutable reference
      |
    1 | fn bad(n: &mut i32) { *n += 1; }
      |            +++
    
    warning: variable does not need to be mutable
     --> <实验目录>/src/u19b.rs:2:17
      |
    2 | fn main() { let mut x = 1; bad(&x); }
      |                 ----^
      |                 |
      |                 help: remove this `mut`
      |
      = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default
    
    error: aborting due to 1 previous error; 1 warning emitted
    …(共 24 行)

两条规则

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let mut s = String::from("hi");
      3 |     let a = &s;
      4 |     let b = &mut s;
      5 |     b.push('!');
      6 |     println!("{a} {b}");
      7 | }
  编译:失败
    error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
     --> <实验目录>/src/u19c.rs:4:13
      |
    3 |     let a = &s;
      |             -- immutable borrow occurs here
    4 |     let b = &mut s;
      |             ^^^^^^ mutable borrow occurs here
    5 |     b.push('!');
    6 |     println!("{a} {b}");
      |                - immutable borrow later used here
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0502`.
  源码:
      1 | fn main() {
      2 |     let mut s = String::from("hi");
      3 |     let a = &mut s;
      4 |     let b = &mut s;
      5 |     a.push('1'); b.push('2');
      6 |     println!("{s}");
      7 | }
  编译:失败
    error[E0499]: cannot borrow `s` as mutable more than once at a time
     --> <实验目录>/src/u19d.rs:4:13
      |
    3 |     let a = &mut s;
      |             ------ first mutable borrow occurs here
    4 |     let b = &mut s;
      |             ^^^^^^ second mutable borrow occurs here
    5 |     a.push('1'); b.push('2');
      |     - first borrow later used here
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0499`.

规则为什么成立:共享多个读者安全(见多个不可变借用为什么可以共存),可变借用必须独占(见可变借用为什么不能同时存在)。

借用持续多久

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let mut s = String::from("hi");
      3 |     let a = &s;
      4 |     println!("a = {a}");        // a 的最后一次使用
      5 |     let b = &mut s;             // 此刻 a 已经不再使用,所以允许
      6 |     b.push('!');
      7 |     println!("b = {b}");
      8 | }
  编译:成功
  运行(退出码 0):
    a = hi
    b = hi!

编译器按引用的最后一次使用来计算借用范围,见NLL 非词法生命周期。

重借用

实测 · 本机执行输出
  源码:
      1 | fn inc(n: &mut i32) { *n += 1; }
      2 | fn main() {
      3 |     let mut x = 0;
      4 |     let r = &mut x;
      5 |     inc(r);            // 隐式重借用:r 之后仍然可用
      6 |     inc(r);
      7 |     *r += 10;
      8 |     println!("x = {}", x);
      9 | }
  编译:成功
  运行(退出码 0):
    x = 12
  源码:
      1 | fn consume(r: &mut i32) { *r += 1; }
      2 | fn main() {
      3 |     let mut x = 0;
      4 |     let r = &mut x;
      5 |     let moved = r;     // 这里是移动,不是重借用
      6 |     consume(moved);
      7 |     consume(r);
      8 | }
  编译:失败
    error[E0382]: borrow of moved value: `r`
     --> <实验目录>/src/u19g.rs:7:13
      |
    4 |     let r = &mut x;
      |         - move occurs because `r` has type `&mut i32`, which does not implement the `Copy` trait
    5 |     let moved = r;     // 这里是移动,不是重借用
      |                 - value moved here
    6 |     consume(moved);
    7 |     consume(r);
      |             ^ value borrowed here after move
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0382`.

分步演示

借用规则
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。错误信息为原样的 rustc 1.98.1 输出
    • 没有验证:两阶段借用(v.push(v.len()) 为何合法)、对结构体不同字段的同时可变借用、闭包捕获时的借用、RefCell 把检查推迟到运行时的行为(见其它文章)
    京ICP备2026067502号-1京公网安备11010502063487号