2. 变量绑定、可变性与作用域实践

变量绑定、可变性与作用域实践 学习卡片

从一段可编译的程序开始

下面的程序把 let、mut、shadowing 和块级作用域放在同一个 main 里,通过打印结果观察变量在生命周期中的变化。请先新建一个二进制项目,把代码写入 src/main.rs。

fn main() {
    // 1. 不可变绑定:默认不可修改
    let x = 5;
    println!("x = {x}");

    // 2. 可变绑定:mut 允许重新赋值,但类型不能变
    let mut count = 0;
    count += 1;
    count += 1;
    println!("count = {count}");

    // 3. shadowing:用 let 重新绑定同名变量,可改变类型
    let x = x + 1;          // 新变量遮蔽旧 x,旧 x 生命周期结束
    let x = x * 2;
    println!("shadowed x = {x}");

    // 4. 块级作用域:内层变量只在内层块中有效
    let outer = 10;
    {
        let inner = outer + 5;
        println!("inner = {inner}");
        // inner 在此块结束后被丢弃
    }
    // println!("{inner}"); // 取消注释会编译失败

    // 5. 作用域与所有权前置约束
    let s = String::from("rust");
    {
        let s = s;          // 把外层 s 移动进内层,外层 s 失效
        println!("inner s = {s}");
    }
    // println!("{s}");     // 取消注释会编译失败:s 已被移动
}

运行 cargo run,预期输出为:

x = 5
count = 2
shadowed x = 12
inner = 15
inner s = rust

用编译器错误验证约束

Rust 的变量规则不是靠运行时检查,而是编译期强制。把下面三行分别取消注释,观察 cargo build 的报错信息,可以确认每条规则对应的错误类型。

取消注释的代码编译器错误说明
x = 6;E0384 cannot assign twice to immutable variable不可变绑定不能二次赋值
println!("{inner}");E0425 cannot find value `inner` in this scope块级作用域外变量不可见
println!("{s}");E0382 borrow of moved value: `s`String 被移动后原绑定失效
注意:let x = x + 1; 中的右侧 x 仍然是旧变量,新绑定在语句结束后才生效。shadowing 不是修改,而是创建新变量并遮蔽旧名字。

变量生命周期与作用域关系

下图把上述程序中的绑定、作用域边界和所有权移动画在同一张时间轴上,帮助区分“遮蔽”和“移动”这两种容易混淆的现象。

变量绑定、作用域与所有权移动时间轴 main 开始 main 结束 ① let x ② let mut count ③ shadowing ④ 块作用域 ⑤ 移动 x = 5 (immutable) 不可重新赋值 count = 0 (mut) 可 += 1 两次 → 2 let x = x + 1 → 6 let x = x * 2 → 12 新绑定遮蔽旧绑定,类型可不同 outer = 10 inner = 15 块结束 → inner 丢弃 s = "rust" s 移入内层 外层 s 失效 shadowing 与移动的关键区别 Shadowing(遮蔽) • 用 let 创建新变量,旧变量仍存在但不可访问 • 可以改变类型:let x = 5; let x = "hi"; • 旧变量在作用域结束时才真正释放 • 不涉及所有权转移 Move(移动) • 把值的所有权从一个绑定转给另一个 • 原绑定立即失效,不能再使用 • 类型不变,只是所有权归属改变 • 编译器用 E0382 阻止使用已移动的值

实践检查清单

  • 确认 let x = 5; 后直接 x = 6; 会触发 E0384,理解“默认不可变”是编译期约束。
  • 确认 let mut count = 0; 后 count += 1; 可以编译,且 count 类型始终为整数。
  • 确认 shadowing 后新变量可以改变类型,例如 let x = 5; let x = "text"; 合法。
  • 确认块内 let inner 在块外不可见,触发 E0425。
  • 确认 String 被移动后原绑定不可用,触发 E0382,理解所有权前置约束。
如果想让外层 s 在块结束后仍然可用,可以改用引用 let r = &s;,这样只借用不移动;引用与生命周期的细节将在后续案例中展开。