☰
Rust 表达式与语句(Expressions):Rust by Example 中的语句、块表达式与求值语义详解
2026/10/6 7:46:59 网站建设 项目流程
  • 文档
  • 教程

【免费下载链接】rust-by-example

Learn Rust with examples (Live code editor included)

项目地址:https://gitcode.com/gh_mirrors/ru/rust-by-example
点击查看免费下载

Rust 程序(几乎完全)由一系列语句(statements)组成,而语句中又嵌入了表达式(expressions)——这是理解 Rust 一切语法的基础。本篇技术指南以 rust-by-example 仓库中 "Expressions" 章节(src/expression.md)为骨架,深入讲解语句与表达式的区分、块(block)作为表达式的赋值行为、分号对求值结果的抑制,并结合仓库中if-else、match、loop、while let、函数返回值等章节的源码级示例,帮助你彻底掌握"块即表达式""末尾分号返回()"这两个 Rust 最核心的语法心智模型。

1. 什么是语句(Statement)

在 Rust 中,程序主体由一条条语句构成。最基本的程序骨架就是函数体内的连续语句:

fn main() { // statement // statement // statement }

语句本身不产生值(或者说产生单元值()),它们用于组织动作:声明变量、执行表达式、控制流程等。Rust 中最常见的两类语句是:

  1. 变量绑定(variable binding)语句:通过let关键字把值绑定到名字上;
  2. 表达式语句(expression statement):一个表达式后面紧跟分号;,使其作为一个"动作"被执行,丢弃其求值结果。

仓库原文给出的完整示例:

fn main() { // variable binding let x = 5; // expression; x; x + 1; 15; }

这里x;、x + 1;、15;都是"表达式 + 分号"形式的语句:它们各自完成一次求值,但结果被直接丢弃。注意变量绑定语句右侧的5本身是表达式,而整条let x = 5;才是语句。

2. 表达式(Expression)与表达式语句

与语句相对,表达式是会产生值的代码片段。在 Rust 中:

  • 字面量是表达式:5、15、true;
  • 运算符调用是表达式:x + 1;
  • 函数调用是表达式:println!("...");
  • 控制结构(if、match、loop、代码块{})也都是表达式;
  • 表达式语句通过末尾的分号把"产生值的表达式"转换为"不产生值的语句"。

正因为"表达式会求值"与"语句不产生值"这对对立关系,分号;在 Rust 中有着极其精确的语义:表达式末尾加分号,表示"求值这个表达式,但丢弃其结果";不加分号,则结果被保留下来,可以成为块或函数的返回值。

3. 块(Block)也是表达式:值传递与()返回

Rust 中一对花括号{}围起来的代码块本身就是一种表达式。因此,块可以用在赋值语句的右侧,其最后一个表达式的求值结果会成为整个块的值,并被赋给左侧的 place expression(例如局部变量)。

仓库原文的核心示例(可复制到 Rust Playground 或本地rustc直接运行):

fn main() { let x = 5u32; let y = { let x_squared = x * x; let x_cubed = x_squared * x; // This expression will be assigned to `y` x_cubed + x_squared + x }; let z = { // The semicolon suppresses this expression and `()` is assigned to `z` 2 * x; }; println!("x is {:?}", x); println!("y is {:?}", y); println!("z is {:?}", z); }

运行结果与逐行剖析:

  • let y = { ... };:块内先通过两条语句计算x_squared = 25、x_cubed = 125,块的最后一行x_cubed + x_squared + x没有分号,因此它的值125 + 25 + 5 = 155成为块表达式的值,被绑定到y,输出y is 155;
  • let z = { 2 * x; };:块内最后一行2 * x;带分号,属于表达式语句,其求值结果被抑制丢弃,块退化为返回单元类型(),因此z的类型是(),输出z is ()。

3.1 分号的"抑制"语义

上述z的例子完美展示了分号的抑制作用:同一个表达式2 * x,去掉分号它会作为块的值返回(类型u32),带上分号它就被当作语句执行、结果被丢弃,块的值变成()。这正是 Rust 中极易踩坑、也极其关键的一个细节——末尾是否加分号,直接决定块/函数返回值的类型。

3.2 与"先声明后初始化"规则的呼应

块的这种"值传递"能力还和仓库 variable_bindings/declare.md 章节所讲的"先声明、后初始化"规则配合使用:let a_binding;可以先声明,稍后(甚至在内层块中)初始化,但在使用前必须完成初始化,否则编译器会直接报错——因为对未初始化绑定的使用会导致未定义行为(undefined behavior)。例如:

fn main() { let a_binding; // 先声明 { let x = 2; a_binding = x * x; // 后初始化 } println!("a binding: {}", a_binding); let another_binding; println!("another binding: {}", another_binding); // 编译错误:使用未初始化绑定 another_binding = 1; }

这条规则同样适用于块表达式内部的绑定使用,是理解"块值传递"的前提约束。

4. 控制结构都是表达式:if、match、loop与函数返回

理解了"块是表达式"之后,Rust 中大量控制结构"可作为值"的设计就顺理成章了。仓库后续章节从多个角度印证了这一设计。

4.1if-else作为表达式(flow_control/if_else.md)

if-else在 Rust 中是表达式,所有分支必须返回相同类型,且可以作为let绑定的右侧值:

fn main() { let n = 5; let big_n = if n < 10 && n > -10 { 10 * n // 该分支返回 i32 } else { n / 2 // 该分支也必须返回 i32 }; // ^ 注意:整个 if-else 表达式结束时要加分号,let 绑定需要它 println!("{} -> {}", n, big_n); }

两个细节值得强调:

  • 各分支末尾的表达式不带分号,其值成为分支值;一旦给某个分支末尾加上分号,该分支就返回(),会与另一分支的i32类型不一致而编译失败(仓库原文以 TODO 注释提示读者尝试);
  • 整个if-else表达式自身要以分号结尾,因为它是let big_n = ...;语句的一部分。

4.2match作为表达式(flow_control/match.md)

match同样是表达式,第一个匹配的分支会被求值,且所有可能取值必须被覆盖(穷尽性检查):

fn main() { let number = 13; match number { 1 => println!("One!"), 2 | 3 | 5 | 7 | 11 => println!("This is a prime"), 13..=19 => println!("A teen"), _ => println!("Ain't special"), // 通配分支,保证穷尽 } let boolean = true; let binary = match boolean { // match 作为表达式赋给 binary false => 0, true => 1, }; println!("{} -> {}", boolean, binary); }

由于match是表达式,它常被用来在赋值时直接产出值,而_ => ...通配分支则确保"所有可能取值都被覆盖"这一穷尽性要求得到满足。

4.3loop作为表达式:用break value返回值(flow_control/loop/return.md)

无限循环loop也是表达式,可以在break后携带一个值,作为整个loop表达式的值返回:

fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; // break 携带值,loop 表达式返回该值 } }; assert_eq!(result, 20); }

这种"重试直到成功,成功时把结果传出去"的写法,正是loop作为表达式最常见的实战用途。同时,flow_control/loop.md 中break/continue的基础用法(跳出循环、跳过本次迭代)依然照常生效。

4.4 函数返回值:"最后一个表达式"即返回值(fn.md)

函数体内最后一个表达式的值就是函数返回值,这与块表达式的规则完全一致;return关键字则用于提前返回。仓库中的 FizzBuzz 示例清晰地展示了这一点:

fn main() { fizzbuzz_to(100); } fn is_divisible_by(lhs: u32, rhs: u32) -> bool { if rhs == 0 { return false; // 提前返回 } lhs % rhs == 0 // 最后的表达式即返回值,无需 return } fn fizzbuzz_to(n: u32) { // 返回 () 时,返回类型可省略 for n in 1..=n { fizzbuzz(n); } }

注意fizzbuzz_to这类"没有返回值的函数",其返回类型就是单元类型()——这恰好呼应了第一节中"表达式语句产生()"的规则:当函数以println!(...)这类带分号的语句收尾时,函数整体返回()。

5.()单元类型与"永不返回"类型!的对照

()(unit,单元类型)在表达式体系中扮演着"无值之值"的角色:它只有一个可能的值,即()本身。块末尾带分号时返回()、无返回值的函数返回(),都是这一类型的典型来源。

与之相对,仓库 fn/diverging.md 中介绍的类型!(never type,永不返回类型)是"没有任何值"的空类型:发散函数(如panic!、loop {}、进程终止函数)以!作为返回类型,它永远不把控制权交还给调用者。由于!可以被强制转换为任何类型,它可以在match分支等"需要精确类型"的场景中灵活使用——例如continue表达式类型为!,却可以出现在要求u32的match分支里而不违背类型约束:

fn sum_odd_numbers(up_to: u32) -> u32 { let mut acc = 0; for i in 0..up_to { let addition: u32 = match i % 2 == 1 { true => i, // 类型 u32 false => continue, // 类型 !,可转为 u32,合法 }; acc += addition; } acc }

理解()与!的区别,是读懂"分号抑制表达式""发散分支参与类型推断"的关键前提。

6. 表达式体系在模式匹配语法糖中的延伸

"表达式/语句"的心智模型还延伸到仓库的多个模式匹配语法糖章节中:

  • if let:if let本质上是"只关心单个模式匹配"的match语法糖。它可以带else(匹配失败分支)、else if(多重失败条件),也能对Option、自定义枚举做解构绑定,甚至在枚举未实现/未派生PartialEq时依然可用(而if Foo::Bar == a会因无法比较而编译失败);
  • let-else(Rust 1.65 起稳定):let pattern = expr else { 发散分支 };允许可反驳模式(refutable pattern)匹配成功后在外围作用域绑定变量,匹配失败则执行发散分支(break、return、panic!等)。它与match/if let-else的最大区别在于绑定作用域:用match近似实现同样的逻辑往往需要额外的外层let和重复代码;
  • while let:把"loop+match反复解构、失败即break"的冗长写法压缩为while let Some(i) = optional { ... },显著减少右移缩进(rightward drift),且无需显式处理失败分支。

这三者都建立在"表达式求值、块即表达式、分号控制返回"的统一语义之上:if let/while let的块体、let-else的发散分支,都是块表达式的具体应用。

7. 实践验证:如何在当前仓库中运行这些示例

本仓库rust-by-example是官方 Rust 教程的镜像仓库,所有示例均以rust,editable代码块形式编写,可直接在 mdBook 构建的在线书籍中通过内置编辑器运行,也可复制到本地使用rustc或 Cargo 运行:

# 将示例代码保存为 main.rs 后 rustc main.rs && ./main

仓库的构建与阅读方式:本仓库使用 book.toml 配置 mdBook("Expressions" 章节位于 src/expression.md,是 "Conversion" 与 "Flow of Control" 之间的独立小节)。若要在本地构建阅读,可安装 mdBook 后在仓库根目录执行mdbook build,产物输出到book/目录。注意本仓库为只读镜像,示例的运行与实验请在本地副本中完成。

8. 小结:一份"语句/表达式"速查清单

语法形态是否表达式求值结果典型来源章节
变量绑定let x = 5;否(是语句)()expression.md、declare.md
表达式 + 分号(如x;、15;)否(表达式语句)值被丢弃,语句产生()expression.md
裸表达式(不带分号)是表达式的值expression.md、fn.md
块{ ... }是最后一个表达式(无分号)的值;末尾带分号则为()expression.md
if-else是各分支值(必须同类型)if_else.md
match是匹配分支的值(必须穷尽)match.md
loop是break value携带的值;break无值则为()loop/return.md
函数体是(体即块)最后一个表达式;return提前返回fn.md

掌握这张清单,你就掌握了 Rust 表达式体系的全部主干:一切皆可求值,块是值的容器,分号决定值是否被保留,()是"无值"的显式化。这也是继续阅读本仓库后续章节(flow_control、fn、scope 等)时反复出现的基础语义。

  • 文档
  • 教程

【免费下载链接】rust-by-example

Learn Rust with examples (Live code editor included)

项目地址:https://gitcode.com/gh_mirrors/ru/rust-by-example
点击查看免费下载

相关推荐

上一篇:vJoy虚拟摇杆:打破物理限制的Windows游戏控制器编程利器
下一篇:VisualGGPK2:5分钟解锁《流放之路》游戏资源编辑新姿势

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询