bigywater.tech
Rust · 4

Shadowing 与 mut 的区别

再写一次 let 和写 let mut 看起来都是「改一个变量」,实际差别很大。这篇用真实的编译与运行结果比较两者:能不能换类型、作用域、是不是同一个变量,以及一个常被忽略的点 —— 被遮蔽的值什么时候释放。

E0308
mut 想把 &str 变成 usize 时的错误;shadowing 可以
新变量
shadowing 是另一个变量(debug 下地址不同),mut 是同一个
不提前释放
被遮蔽的值仍活到作用域结束才 drop

换类型

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let spaces = "   ";
      3 |     let spaces = spaces.len();   // &str → usize:新变量,新类型
      4 |     println!("spaces = {spaces}");
      5 | }
  编译:成功
  运行(退出码 0):
    spaces = 3
  源码:
      1 | fn main() {
      2 |     let mut spaces = "   ";
      3 |     spaces = spaces.len();       // 想用 mut 把 &str 变成 usize
      4 |     println!("spaces = {spaces}");
      5 | }
  编译:失败
    error[E0308]: mismatched types
     --> <实验目录>/src/u4b.rs:3:14
      |
    2 |     let mut spaces = "   ";
      |                      ----- expected due to this value
    3 |     spaces = spaces.len();       // 想用 mut 把 &str 变成 usize
      |              ^^^^^^^^^^^^ expected `&str`, found `usize`
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0308`.

shadowing 的典型用途就是「把解析前的字符串变成解析后的数」而不必起两个名字(见Option 与 Result里的 parse)。

作用域

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let x = 5;
      3 |     let x = x + 1;
      4 |     {
      5 |         let x = x * 2;
      6 |         println!("内层 x = {x}");
      7 |     }
      8 |     println!("外层 x = {x}");
      9 | }
  编译:成功
  运行(退出码 0):
    内层 x = 12
    外层 x = 6

新变量还是同一个变量

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let x = 1u64;
      3 |     let p1 = &x as *const u64 as usize;
      4 |     let x = x + 1;
      5 |     let p2 = &x as *const u64 as usize;
      6 |     println!("shadowing:两次 x 的地址相同吗? {}", p1 == p2);
      7 |     let mut y = 1u64;
      8 |     let q1 = &y as *const u64 as usize;
      9 |     y += 1;
     10 |     let q2 = &y as *const u64 as usize;
     11 |     println!("mut:两次 y 的地址相同吗? {}", q1 == q2);
     12 | }
  编译:成功
  运行(退出码 0):
    shadowing:两次 x 的地址相同吗? false
    mut:两次 y 的地址相同吗? true

地址只是一个旁证,不是语言保证。

被遮蔽的值什么时候释放

实测 · 本机执行输出
  源码:
      1 | struct D(&'static str);
      2 | impl Drop for D { fn drop(&mut self) { println!("drop {}", self.0); } }
      3 | fn main() {
      4 |     let a = D("第一个");
      5 |     println!("创建第一个");
      6 |     let a = D("第二个");
      7 |     println!("遮蔽之后,第一个还在吗?(还没有 drop 它)");
      8 |     let _ = &a;
      9 |     println!("main 结束");
     10 | }
  编译:成功
  运行(退出码 0):
    创建第一个
    遮蔽之后,第一个还在吗?(还没有 drop 它)
    main 结束
    drop 第二个
    drop 第一个

Drop 的顺序规则见Drop Trait 与资源释放。

遮蔽与移动

实测 · 本机执行输出
  源码:
      1 | fn main() {
      2 |     let s = String::from("hello");
      3 |     let s = s + ", world";      // 旧 s 被移动进 + ,新 s 是结果
      4 |     println!("{s}");
      5 | }
  编译:成功
  运行(退出码 0):
    hello, world

分步演示

shadowing 与 mut
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。第 3 节用 -C opt-level=0 编译;其余用默认(debug)构建
    • 没有验证:优化构建下的地址与栈布局、闭包捕获被遮蔽变量、模式匹配中的遮蔽、for 循环变量的遮蔽
    京ICP备2026067502号-1京公网安备11010502063487号