bigywater.tech
Rust · 21

可变借用为什么不能同时存在

为什么同一时刻只能有一个 &mut?除了防止数据竞争,独占性还给编译器带来了好处。这篇从四个角度验证:最常见的违规场景(迭代时修改)、合法的拆分(split_at_mut)、独占性换来的优化(汇编里少一次内存读取),以及 &mut 不能复制。

E0499
两个 &mut 重叠时的错误
mov w0, #1
&mut 版本直接返回常量;裸指针版本要 ldr 重新读
split_at_mut
拆成不相交的两个 &mut 是合法的

迭代时修改

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let mut v = vec![1, 2, 3];
      3 |     for x in &v {
      4 |         if *x == 2 { v.push(99); }
      5 |     }
      6 |     println!("{:?}", v);
      7 | }
  编译:失败
    error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
     --> <实验目录>/src/u21a.rs:4:22
      |
    3 |     for x in &v {
      |              --
      |              |
      |              immutable borrow occurs here
      |              immutable borrow later used here
    4 |         if *x == 2 { v.push(99); }
      |                      ^^^^^^^^^^ mutable borrow occurs here
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0502`.

别名与 split_at_mut

实测 · 本机执行输出
  源码:
      1 | fn swap_halves(a: &mut [i32], b: &mut [i32]) { for (x, y) in a.iter_mut().zip(b.iter_mut()) { std::mem::swap(x, y); } }
      2 | fn main() {
      3 |     let mut v = vec![1, 2, 3, 4];
      4 |     swap_halves(&mut v, &mut v);       // 两个 &mut 指向同一个 Vec
      5 |     println!("{:?}", v);
      6 | }
  编译:失败
    error[E0499]: cannot borrow `v` as mutable more than once at a time
     --> <实验目录>/src/u21b.rs:4:25
      |
    4 |     swap_halves(&mut v, &mut v);       // 两个 &mut 指向同一个 Vec
      |     ----------- ------  ^^^^^^ second mutable borrow occurs here
      |     |           |
      |     |           first mutable borrow occurs here
      |     first borrow later used by call
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0499`.
  源码:
      1 | fn swap_halves(a: &mut [i32], b: &mut [i32]) { for (x, y) in a.iter_mut().zip(b.iter_mut()) { std::mem::swap(x, y); } }
      2 | fn main() {
      3 |     let mut v = vec![1, 2, 3, 4];
      4 |     let (left, right) = v.split_at_mut(2);      // 把一个切片拆成两个不相交的 &mut
      5 |     swap_halves(left, right);
      6 |     println!("{:?}", v);
      7 | }
  编译:成功
  运行(退出码 0):
    [3, 4, 1, 2]

独占带来的优化

实测 · 本机执行输出
  with_mut(&mut, &mut)  汇编: mov	w8, #1 ; str	w8, [x0] ; mov	w8, #2 ; str	w8, [x1] ; mov	w0, #1 ; ret
  with_ptr(*mut, *mut)  汇编: mov	w8, #1 ; str	w8, [x0] ; mov	w8, #2 ; str	w8, [x1] ; ldr	w0, [x0] ; ret
  (用 &mut 时编译器知道 a 与 b 不会指向同一处,所以敢直接返回常量 1;用裸指针时 a、b 可能相同,必须在写 *b 之后重新读 *a)

汇编用 --emit=asm -O 生成;只是一个很小的例子,不能据此估计实际程序里的收益。

不是 Copy

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let mut x = 0;
      3 |     let a = &mut x;
      4 |     let b = a;       // &mut 被移动
      5 |     *b += 1;
      6 |     *a += 1;
      7 | }
  编译:失败
    error[E0382]: use of moved value: `a`
     --> <实验目录>/src/u21e.rs:6:5
      |
    3 |     let a = &mut x;
      |         - move occurs because `a` has type `&mut i32`, which does not implement the `Copy` trait
    4 |     let b = a;       // &mut 被移动
      |             - value moved here
    5 |     *b += 1;
    6 |     *a += 1;
      |     ^^^^^^^ value used 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,数字每次运行会有小幅抖动。汇编目标 aarch64-apple-darwin
    • 没有验证:noalias 对真实程序性能的影响、LLVM 在不同版本对 noalias 的使用、&mut 与 UnsafeCell 的关系、Stacked Borrows / Tree Borrows 模型(需要 Miri)
    京ICP备2026067502号-1京公网安备11010502063487号