常量与 Shadowing
本教程共 78 篇 · 第 8 篇 · 更新于 2026-08-08 · 约 7 分钟阅读
本节目标:分清
const常量和不可变变量的差别,并掌握 shadowing(同名重绑定)这个实用技巧,避免和mut搞混。
上一章说变量默认不可变,有人就问:那不就是常量吗?不是。Rust 里”常量”是另一个东西,用 const 声明;另外还有一种叫 shadowing 的玩法,容易和”改值”混淆。这一章一次讲清。
8-1 声明常量用 const
常量长这样:
const MAX_POINTS: u32 = 100_000;
规矩比变量严得多:
- 用
const而不是let。 - 必须标注类型,不能靠推断。
- 值必须能在编译期就算出来,不能是运行时才能确定的结果(比如不能调用一个会读文件的函数的返回值)。
- 命名惯例是全大写加下划线。
Note
100_000里的下划线是 Rust 允许的”数字分隔符”,纯粹为了好看,编译器当它是 100000。大数字这么写一眼就读得清。
8-2 常量和不可变变量的三大区别
别看”不可变变量”也改不了,它和常量不是一回事:
- 声明关键字不同:变量
let,常量const。 - 作用域位置不同:常量可声明在任意作用域,包括全局(函数外面);而
let变量一般声明在函数内。常量常驻整个程序生命周期。 - 值的确定时机不同:常量的值在编译期就固定死;不可变变量的值虽然后来不能改,但它是运行时才绑定的。
一句话:常量是”编译期写死的全局定值”,变量是”运行期绑定但此后不改的局部值”。
Tip经验法则:一个值在整个程序里都该是同一个固定数(比如圆周率、最大连接数),用
const;只是某段逻辑里暂时不改的,用let。
8-3 什么是 Shadowing
shadowing 翻译叫”重影”或”遮蔽”。意思是:你可以用同一个名字再声明一次 let,后面的会”盖住”前面的:
fn main() {
let x = 5;
let x = x + 1; // 新 x 遮蔽旧 x,值是 6
let x = x * 2; // 再遮蔽,值是 12
println!("x 最终是 {x}");
}
输出 x 最终是 12。这里每次都是全新的 let x,不是给老 x 赋值。旧 x 在它被遮蔽后就看不见了,但内存上它已被新绑定取代。
8-4 Shadowing 能改变类型
shadowing 最妙的地方:可以在”重绑”时换类型,这是 mut 做不到的:
fn main() {
let spaces = " "; // 这是字符串
let spaces = spaces.len(); // 重绑成数字(空格个数)
println!("空格数是 {spaces}");
}
第一个 spaces 是字符串,第二个 spaces 是整数。如果用 mut 写 let mut spaces = " "; spaces = spaces.len(); 会报错,因为类型不能变。shadowing 绕过了这个限制,因为它本质上是”新声明”,不是”改旧值”。
Warning别把 shadowing 和
mut混为一谈。mut是同一个变量改值、类型不变;shadowing 是同名的新变量把旧变量遮住,连类型都能换。初学者常以为let x = x + 1是给 x 加一,其实它是拿旧 x 算出新值再绑回 x。
8-5 shadowing 的典型用途
最常见的场景是”先拿到一个原始输入,再加工成最终形态”。比如读取用户输入是字符串,你用 shadowing 把它转成整数:
let input = "42"; // 字符串
let input: u32 = input.trim().parse().unwrap(); // 转成整数,类型也换了
虽然名字没变,语义上完成了”从文本到数字”的转换。代码读起来顺,名字还不用绞尽脑汁另起。
8-6 作用域内的遮蔽范围
shadowing 只在它所在的作用域及其内层生效。离开作用域,旧值就回来了——但真正理解作用域要到后面章节,这里先知道”遮蔽有范围”即可,不必钻牛角尖。
8-7 三者怎么选
- 值固定不变、全局通用 →
const。 - 局部值、编完后不改 →
let(默认不可变)。 - 局部值、确实需要改 →
let mut。 - 想在同名下做”类型转换/加工重绑” → shadowing(再写一次
let)。
Tip新手容易滥用
mut。其实很多”改值”需求,用 shadowing 表达更清晰,也更符合 Rust 鼓励不可变的习惯。多想想能不能用 shadowing 代替mut。
8-8 常量与 shadowing 的搭配技巧
常量配合 shadowing 有个常见写法:先用 const 定义全局基准,再用 shadowing 在局部做换算。比如游戏里先 const MAX_HP: u32 = 100;,某函数里 let max_hp = MAX_HP / 2; 表示”当前关卡半血上限”,名字复用、语义清晰,且没引入 mut。
不过要小心别过度 shadowing。如果一个名字在函数里被重绑四五次,读代码的人要不停回想”现在的 x 到底是第几个 x”。经验是:shadowing 两三次以内没问题;再多就说明该换个有意义的变量名,或者把逻辑拆成小函数了。
Noteshadowing 产生的是全新绑定,旧绑定被遮蔽后不能再被引用,编译器保证你不会误用到”已经被盖住”的那个值。它和生命周期有微妙关系,但那属于进阶话题,现阶段先掌握”同名重绑、可换类型”即可。
8-9 常量命名与误区
常量命名有个强约定:全大写加下划线,比如 MAX_POINTS、PI、DEFAULT_TIMEOUT。这和其他语言的常量风格一致,一眼就能认出”这是个编译期定值”。
一个常见误区是拿 const 当”全局可变变量”用——不行。const 绝不能加 mut,它从定义到程序结束都是固定值。如果你需要”一个全局、运行期会变的量”,那超出了入门范围(涉及 static 与内部可变性),现阶段记住 const 就是死值即可。
Warning别在
const里写依赖运行时输入的表达式(比如const X: i32 = read_number();),编译器会拒绝,因为常量必须在编译期就能算出来。常量只放”写死在代码里的数字/字符串”。需要运行期确定的,用let变量。
8-10 shadowing 的边界
shadowing 虽好,也有被滥用的边界。如果一个变量名在函数里被反复重绑四五次,读代码的人要不停回想”现在的 x 到底是第几个 x、什么类型”,认知负担会很重。经验法则:shadowing 两三次以内是清爽的;再多,就该给中间结果起不同的、有含义的名字,或把逻辑拆成小函数。
Warning另一个隐患:shadowing 会让调试时”变量的值”变得依赖”执行到哪一行”,因为你同一个名字在不同时刻指向不同值。如果只是想改值,用
let mut往往比连续 shadowing 更容易在调试器里跟踪。shadowing 最适合”类型转换”这类语义清晰的场景,别把它当通用赋值用。
8-11 小结
常量 const 是编译期写死的全局定值;shadowing 是用同名 let 重新绑定、连类型都能换。它俩和 mut 各管一摊。下一章进入”数据类型”,我们先看标量类型里的整数和浮点数。