Rustlings 变量练习详解:Rust 的不可变性与mut、遮蔽(Shadowing)与常量
【免费下载链接】rustlings:crab: Small exercises to get you used to reading and writing Rust code!项目地址: https://gitcode.com/gh_mirrors/ru/rustlings
Rust 语言中,变量默认是不可变的(immutable),这一设计直接决定了所有 Rust 程序的编写方式。本文以 Rustlings 仓库中 exercises/01_variables/README.md 为核心,结合 6 个循序渐进的小练习(variables1 ~ variables6)、官方参考答案(solutions/01_variables/)与练习提示配置(rustlings-macros/info.toml),系统讲解变量的声明、类型推断、可变性、遮蔽以及常量与变量的区别。读完本文,你不仅能独立完成 01_variables 全部练习,还能理解 Rust 编译器在背后替你拦下了哪些易犯错误。
Rust 变量:默认不可变
与多数编程语言不同,Rust 中的变量绑定(variable binding)默认是不可变的。这意味着一旦把一个值绑定到某个名字上,就不能再修改这个值。Rustlings 的变量练习 README 开宗明义地写道:
In Rust, variables are immutable by default. When a variable is immutable, once a value is bound to a name, you can't change that value. You can make them mutable by adding
mutin front of the variable name.
即:
- 变量默认不可变;一旦值绑定到名字,就不能再改变它;
- 通过在变量名前添加
mut关键字,可以让变量变为可变。
这一设计是 Rust 内存安全承诺的重要组成部分:不可变性消除了"某个值在读取时被其他代码意外修改"这一类竞态与逻辑错误,让编译器可以在编译期就拒绝大量不安全的改写。
需要说明的是,"不可变"针对的是绑定(binding)本身:变量名不能再被重新赋值,而非该值内部的字段不可修改。真正的运行时只读约束需要借助const(见后文)或引用类型来实现。
用let声明变量:类型推断与类型注解
Rust 使用let关键字创建新的变量绑定,形式为:
let x = 5; // 类型由编译器推断 let y: i32 = 42; // 显式类型注解- 类型推断:当没有显式注解时,Rust 会根据右侧的值推断类型。整数字面量的默认类型是
i32; - 类型注解:可以在变量名后加
: 类型显式指定,例如let x: u8 = 42;。当需要非默认类型或编译器无法推断时,注解必不可少。
这一点在 variables2 的官方解答中有明确注释(solutions/01_variables/variables2.rs):
let x = 42; // 不注解时,Rust 推断为默认的 i32 // let x: u8 = 42; // 也可以显式注解为其他整数类型未初始化变量不可使用
Rust 禁止读取未初始化的变量。variables3 要求你给let x: i32;赋一个值,否则编译器会直接报错——这是"读取不存在的值"这类 bug 在编译期被拦截的典型例子。官方解答(solutions/01_variables/variables3.rs)还展示了一种合法用法:先声明、后初始化,但初始化前绝不能使用:
let y: i32; // 先声明 y = 42; // 后赋值 println!("Number {y}"); // 此时才可读取用mut让变量可变
如果确实需要修改变量绑定的值,需要在let后加上mut关键字:
let mut x = 3; x = 5; // 合法:x 是可变的variables4 正是这一知识点的实战:原练习中let x = 3;之后紧跟着x = 5;的重新赋值,编译必然失败。正确答案只需要把声明行改为let mut x = 3;(solutions/01_variables/variables4.rs)。
变量练习对应的提示文件 rustlings-macros/info.toml 也直白地指出:Rust 中变量绑定默认不可变,要重新赋值就必须使用mut。
经验法则:仅在明确需要改写绑定的场景下使用
mut;默认保持不可变,能让代码的意图更清晰,也让编译器替你检查更多潜在问题。
遮蔽(Shadowing):同名变量的重新声明
mut解决的是"改变值",但有些场景你其实想要的是一个新的、不同类型的同名变量。Rust 提供了一种更强大的机制:遮蔽(shadowing)——用let重新声明一个同名变量,新绑定会遮蔽旧的,且允许改变类型。
variables5 就是为练习遮蔽而设计的:一开始number是字符串"T-H-R-E-E",随后又要让number参与整数运算number + 2。直接改成mut没有意义,因为类型不匹配。官方解法(solutions/01_variables/variables5.rs)是:
let number = "T-H-R-E-E"; println!("Spell a number: {number}"); let number = 3; // 遮蔽:同名新绑定,类型从 &str 变成 i32 println!("Number plus two is: {}", number + 2);info.toml中的提示(rustlings-macros/info.toml)特别强调了遮蔽的适用场景:当你只是想把值转换成不同类型、或想复用已有变量名时,遮蔽比mut更合适。
遮蔽与mut的关键区别:
| 维度 | mut可变绑定 | 遮蔽(shadowing) |
|---|---|---|
| 关键字 | let mut | 再次使用let声明同名变量 |
| 是否允许换类型 | 不允许(类型一旦推断即固定) | 允许,新绑定可以有全新类型 |
| 生命周期 | 同一绑定持续到作用域结束 | 新绑定创建后旧绑定不可见 |
| 典型场景 | 修改同一类型的值 | 类型转换、复用变量名 |
常量(Constants):用const声明
除了变量,Rust 还有常量。variables6 引入了这一概念,info.toml中的提示(rustlings-macros/info.toml)总结了常量与变量的核心区别:
- 常量用
const声明,而不是let; - 常量永远不可变,不存在
mut版本; - 常量的类型必须显式注解,不能省略;
- 常量可以声明在任何作用域(包括全局),并且必须在编译期就能确定值。
官方解答(solutions/01_variables/variables6.rs):
const NUMBER: u64 = 3; // 类型注解是必需的 fn main() { println!("Number: {NUMBER}"); }变量与常量的对比:
| 维度 | 变量(let) | 常量(const) |
|---|---|---|
| 关键字 | let/let mut | const |
| 可变性 | 默认不可变,可用mut开启 | 永远不可变 |
| 类型注解 | 可省略(类型推断) | 必须显式注解 |
| 声明位置 | 函数体内等局部作用域 | 任意作用域,常用于全局 |
| 值确定时机 | 运行时绑定 | 编译期常量 |
逐题通关:variables1 ~ variables6 完整指南
Rustlings 中,01_variables 目录下的 6 个练习均为test = false的编译型练习(见 rustlings-macros/info.toml),即只需要让代码通过编译即可。下面逐题说明修复思路与官方答案。
variables1:补上let关键字
原代码(exercises/01_variables/variables1.rs)中x = 5;缺少声明关键字。提示指出:在main中创建新变量绑定需要一个关键字。解法是在x = 5;前补上let:
fn main() { let x = 5; println!("x has the value {x}"); }variables2:初始化变量以解决类型推断失败
原代码(exercises/01_variables/variables2.rs)中let x;之后直接参与x == 10的比较,编译器无法推断x的类型。提示引导你思考三种方案:加类型注解、给x赋值、或者两者都做。最简解法是直接初始化:
let x = 42; // 推断为 i32,与 10 同类型也可以显式注解:let x: u8 = 42;。关键点是x的类型必须与10可比(同为整数类型)。
variables3:给变量赋值后才能使用
原代码(exercises/01_variables/variables3.rs)声明了let x: i32;却从未赋值就打印。解法:let x: i32 = 42;。如前面所述,也可以先声明后赋值,但绝不能在使用前保持未初始化。
variables4:用mut允许重新赋值
原代码(exercises/01_variables/variables4.rs)中x = 5;是"不要修改这一行"的重新赋值,所以只能改声明行:
let mut x = 3;variables5:用遮蔽完成类型转换
原代码(exercises/01_variables/variables5.rs)要求"不改变变量名"地修复编译错误,且不能用mut(类型不同)。解法是遮蔽:
let number = "T-H-R-E-E"; let number = 3; println!("Number plus two is: {}", number + 2);variables6:为常量补上类型注解
原代码(exercises/01_variables/variables6.rs)中const NUMBER = 3;缺少类型注解。解法:
const NUMBER: u64 = 3;类型可以换成其他整数类型(如i32),但注解本身不可省略。
在 Rustlings 中运行与验证
这些练习属于"编译通过即算完成"的类型,不包含#[test]断言。在仓库根目录下:
# 进入下一道练习(变量系列从 variables1 开始) rustlings next # 运行当前练习并检查是否正确 rustlings run # 或直接指定练习运行 rustlings run variables1当代码通过编译后,rustlings run会给出成功提示并自动进入下一题。想对照标准答案,可以直接查看 solutions/01_variables/ 目录下对应的同名文件,每份答案都附有解释性注释,是复习知识点的最佳素材。
小结
- Rust 变量默认不可变,这是语言层面的安全设计;
- 需要改写绑定时用
let mut; - 需要换类型或复用名字时用
let遮蔽; - 需要编译期确定的全局不可变值时用
const,且必须写类型注解; - 未初始化变量不可读取,编译器会在编译期拦截这类错误。
Rustlings 的变量练习与官方文档 The Rust Book 的 §3.1 "Variables and Mutability"(对应关系见 exercises/README.md 的练习章节映射表)一一对应。完成这一组练习后,你将具备编写正确、地道的 Rust 变量代码的基础能力。
【免费下载链接】rustlings:crab: Small exercises to get you used to reading and writing Rust code!项目地址: https://gitcode.com/gh_mirrors/ru/rustlings
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考