Rust 作用域
作用域(scope)是变量有效的那一段代码区域。这个概念很简单,但弄懂了它,Rust 的很多其他规则(尤其是所有权)一下子就说得通了。
作用域由花括号划定
Section titled “作用域由花括号划定”一个变量从声明的那一刻开始存在,直到它所在代码块的右花括号 } 为止:
fn main() { let x = 5; // x 从这里开始进入作用域
{ let y = 10; // y 从这里开始进入作用域 println!("x = {x}, y = {y}"); // 两个都有效 } // y 在这里离开作用域
println!("x = {x}"); // println!("{y}"); // ❌ 错误:y 已经不在作用域里了}输出:
x = 5, y = 10x = 5y 只存在于内层的 { } 代码块里。代码块一结束,y 就没了。之后再用它会得到一个编译错误,而不是运行时才冒出来的意外。
作用域是单向的:内层代码块能看到外层代码块的变量(所以内层代码块能打印 x),但外层代码块看不到内层的变量。
函数体也是作用域
Section titled “函数体也是作用域”参数以及你在函数里创建的变量,只在这次函数调用期间存在:
fn calculate() -> i32 { let result = 5 * 2; // result 只存在于 calculate 里面 result}
fn main() { let value = calculate(); println!("{value}"); // println!("{result}"); // ❌ 错误:在这里 result 不存在}输出:
10反过来也一样:函数看不到 main 里的局部变量。如果它需要某个值,就像你在函数那一页看到的那样,把它作为参数传进去。
代码块也是表达式
Section titled “代码块也是表达式”{ } 代码块不只是限定变量的存活范围,它还能产生一个值:也就是块里最后一个不带分号的表达式。这和让函数返回最后一行的规则是同一个。
当你需要几个辅助变量来算出某个结果,又不想让它们之后一直留着时,这个特性就很方便:
fn main() { let price = 40;
let total = { let tax = price / 10; // tax 只活在这个代码块里 let shipping = 5; price + tax + shipping // 没有分号:这就是代码块的值 };
println!("total = {total}"); // println!("{tax}"); // ❌ 错误:tax 已经离开作用域}输出:
total = 49有两个细节要留意:
- 最后一行
price + tax + shipping没有分号。如果加上分号,代码块给出的就是()(什么都没有),而不是一个数字,println!也就没法打印它了。 - 整个写法是一条
let语句,所以在右花括号后面要以};结尾。
代码块一结束,tax 和 shipping 就消失了,只剩下结果 total。像这样把辅助变量放进一个小代码块,能清楚地表明它们只是为了这一次计算。
作用域为什么对所有权很重要
Section titled “作用域为什么对所有权很重要”这和你稍后会学到的所有权直接相关:一个值的所有者离开作用域的那一刻,这个值就会被自动清理掉。
fn main() { { let text = String::from("你好"); println!("{text}"); } // text 的作用域到这里结束,Rust 会自动丢弃它}输出:
你好Rust 不需要垃圾回收器,靠的就是这整套机制:它在编译时就已经准确知道每个作用域在哪里结束。
遮蔽与作用域
Section titled “遮蔽与作用域”还记得变量那一页讲的遮蔽(shadowing)吗?在一个作用域里,每次用同一个名字写 let,都会创建一个新的变量。它不会抹掉外层的变量,只是在这个作用域剩下的部分里接替它:
fn main() { let x = 5; { let x = x * 2; // 一个新的 x,只在这个代码块里 println!("内层 x = {x}"); // 10 } println!("外层 x = {x}"); // 5,不受影响}输出:
内层 x = 10外层 x = 51. 修复作用域错误
Section titled “1. 修复作用域错误”下面这个程序无法编译,因为 area 在它的代码块结束之后还被使用:
fn main() { let width = 3; { let height = 4; let area = width * height; } println!("area = {area}");}修改它,让它打印出 area = 12,同时 height 仍然只活在代码块里。提示:让代码块产生面积,再把结果存进一个在代码块外面声明的变量。
查看答案
fn main() { let width = 3; let area = { let height = 4; width * height }; println!("area = {area}");}输出:
area = 12现在 area 活在 main 的作用域里,所以 println! 能看到它。height 仍然只在代码块里用到,代码块结束时它就被丢弃了。
2. 一层套一层
Section titled “2. 一层套一层”创建一个变量 level,值为 1。打开一个代码块,用 2 遮蔽它;在这个代码块里再打开一个代码块,用 3 遮蔽它。在每个代码块里打印 level,并在每个代码块结束后马上再打印一次。提示:你应该看到数字依次是 1、2、3,然后回到 2 和 1。
查看答案
fn main() { let level = 1; println!("第 {level} 层"); { let level = 2; println!("第 {level} 层"); { let level = 3; println!("第 {level} 层"); } println!("回到第 {level} 层"); } println!("回到第 {level} 层");}输出:
第 1 层第 2 层第 3 层回到第 2 层回到第 1 层每个内层的 level 只在它的代码块结束之前遮住外层的那个。外层变量从来没有被改动过,所以它们一直都在,等着你回来。
- 作用域是变量有效的区域(用
{ }标出)。 - 一到作用域的右花括号
},变量就立刻不复存在。 - 函数体也是作用域:函数返回时,里面的局部变量就消失了。
- 代码块是表达式:它的最后一行(不带分号)就是它的值,块里的辅助变量在块结束时消失。
- 作用域准确地告诉 Rust 什么时候丢弃一个值,所以 Rust 不需要垃圾回收器。
小测验
1. 根据本页内容,Rust 为什么不需要垃圾回收器?
2. 在内层代码块里用 let x = x * 2; 遮蔽一个变量,会发生什么?
3. 执行 let total = { let a = 2; a * 10 }; 之后,total 的值是多少?
得分: 0 / 3
- 变量作用域 — Rust 程序设计语言:讲解作用域的工作方式,作为理解所有权的第一步。
- 作用域和遮蔽 — 通过例子学 Rust:关于代码块和遮蔽的简短可运行示例。
- RAII — 通过例子学 Rust:值在作用域结束时会发生什么,准备好进入下一步时再看。
- Block expressions — The Rust Reference(英文):代码块及其产生的值的精确规则。