06. Expressions — 表达式
Rust 是一门表达式语言(expression language)。这个特性来自 Lisp 的传统:几乎所有语法结构都产生值。在 C 语言中,if 和 while 是语句——它们不返回值,你只能用三元运算符 ?: 做条件求值。在 Rust 中,if、match、loop 都产生值——三元运算符变得多余。
这对代码风格有深远影响:你可以将控制流嵌入到值的使用点,无需提前声明未初始化的变量。
本章系统介绍 Rust 的表达式体系——从块和分号,到控制流表达式,再到运算符和闭包。
表达式 vs 语句
在 Rust 中,几乎所有的东西都是表达式。表达式产生值;语句不产生值(或产生 ())。
// 语句:以分号结尾
let x = 5; // let 语句
println!("hello"); // 表达式语句(表达式的值被丢弃)
// 表达式:不以分号结尾(作为块的尾表达式时)
x + 1 // 算术表达式,产生值
if x > 0 { 1 } else { -1 } // if 表达式,产生值分号 ; 的作用是将表达式转换为语句——它丢弃表达式的值并返回单元类型 ():
let x = {
let y = 10;
y + 1 // 没有分号:块的值为 11
};
println!("{}", x); // 11
let z = {
let y = 10;
y + 1; // 有分号:块的值为 ()
};
// println!("{}", z); // 错误:z 是 (),不能这样打印块表达式
块 { ... } 是最基本的复合表达式。它的值是最后一个表达式的值(如果最后一个表达式以分号结尾则是 ()):
let msg = {
// 一系列语句
let part1 = "hello";
let part2 = "world";
// 最后一个表达式——没有分号——是块的值
format!("{} {}", part1, part2)
};
println!("{}", msg); // "hello world"块中还可以包含条目声明(item declarations)——fn、struct、use 等。这些声明不产生值,但可以在块的作用域内使用:
fn show_files() {
// 在函数体内定义嵌套函数
fn cmp_by_timestamp(a: &FileInfo, b: &FileInfo) -> std::cmp::Ordering {
a.timestamp.cmp(&b.timestamp)
}
// 使用嵌套函数
files.sort_by(cmp_by_timestamp);
}注意:嵌套 fn 不能访问外层作用域的局部变量(那是闭包的工作)。
声明:let
let 声明的完整语法:
let name: Type = expr;类型标注和初始值都可以省略(但不能同时省略):
let x; // 未初始化——后续必须赋值
x = 10;
let y = 20; // 类型推断
let z: i32 = 30; // 完整形式**遮蔽(shadowing)**允许用同名变量重新绑定:
let line = read_line(); // line: String
let line = line.trim(); // line: &str(新的绑定,遮蔽旧的)
let line = line.parse::<i32>()?; // line: i32这种模式在处理管道式数据转换时非常自然——每个步骤产生一个更精炼的值,旧值被遮蔽。
if 与 match 表达式
if 表达式
if 产生值。条件必须是 bool(不允许隐式转换),不要求括号,但大括号是必须的:
let status = if resp.code == 200 {
HttpStatus::Ok
} else if resp.code == 500 {
HttpStatus::ServerError
} else {
HttpStatus::Unknown
};
// status 由 if 表达式赋值
// Rust 不需要三元运算符,因为 if 本身就是表达式
let abs_val = if x >= 0 { x } else { -x };if 的所有分支必须产生相同类型。没有 else 的 if 等价于带有空 else 块的 if——返回 ():
// 这个 if 返回 ()——因为没有 else 分支
if condition {
do_something();
}
// 等价于
if condition {
do_something();
} else {
// 隐式的 else { }
}match 表达式
match 是 Rust 中最强大的控制流结构——对值进行模式匹配,要求穷尽所有可能:
match code {
0 => println!("success"),
1 => println!("pending"),
2 => println!("error"),
_ => println!("unknown"), // 通配符捕获所有剩余情况
}match 也产生值:
let description = match number {
0 => "zero",
1 => "one",
2 => "two",
_ => "many",
};编译器强制穷尽匹配——如果遗漏可能的值,编译失败:
error[E0004]: non-exhaustive patterns: `3_i32..=i32::MAX` not covered
--> src/main.rs:3:11
|
3 | match number {
| ^^^^^^ pattern `3_i32..=i32::MAX` not covered所有分支必须产生相同类型:
// 编译错误:分支类型不一致
// let val = match status {
// Status::Active => 1,
// Status::Inactive => "inactive", // 错误:期望 i32,得到 &str
// };if let
if let 是仅匹配一个模式加一个通配符的 match 的语法糖:
// match 写法
match optional_value {
Some(v) => { /* 使用 v */ }
None => {}
}
// if let 写法——更简洁
if let Some(v) = optional_value {
// 使用 v
}
// 带 else 的 if let
if let Some(v) = optional_value {
println!("Got: {}", v);
} else {
println!("Nothing");
}常见用例:
// 检查 Result
if let Err(e) = do_something() {
eprintln!("Error: {}", e);
}
// 解析可选参数
if let Some(name) = params.get("name") {
println!("Hello, {}!", name);
}循环表达式
Rust 有四种循环形式:
| 循环形式 | 语法 | 用途 |
|---|---|---|
while | while condition { } | 条件循环,条件必须是 bool |
while let | while let pattern = expr { } | 模式匹配循环 |
loop | loop { } | 无限循环(可带 break 值) |
for | for pattern in iterable { } | 迭代循环 |
for 循环
for 是最常用的循环形式,用于遍历任何实现了 IntoIterator 的类型:
// 遍历范围
for i in 0..5 {
println!("{}", i); // 0, 1, 2, 3, 4
}
// 遍历 Vec——消费迭代(获取所有权)
let strings = vec!["a".to_string(), "b".to_string()];
for s in strings { // s: String,strings 被消费
println!("{}", s);
}
// println!("{:?}", strings); // 错误:strings 已被移动
// 遍历引用——不获取所有权
let strings = vec!["a".to_string(), "b".to_string()];
for s in &strings { // s: &String
println!("{}", s);
}
println!("{:?}", strings); // OK
// 遍历可变引用——可以修改
let mut strings = vec!["a".to_string(), "b".to_string()];
for s in &mut strings { // s: &mut String
s.push_str("!");
}
println!("{:?}", strings); // ["a!", "b!"]loop 与 break 值
loop 是唯一可以从中 break 带值的循环:
// 用 loop 实现重试逻辑
let result = loop {
match try_connect() {
Ok(conn) => break conn, // break 带出连接
Err(e) if is_retryable(&e) => {
println!("Retrying...");
continue;
}
Err(e) => {
eprintln!("Fatal: {}", e);
break None; // break 带出 None
}
}
};break 可以带值从 loop 中退出。while 和 for 不支持带值的 break,因为循环体可能一次都不执行。
while let
// 用 while let 处理迭代器
let mut v = vec![1, 2, 3];
while let Some(x) = v.pop() {
println!("{}", x); // 3, 2, 1
}循环标签
在嵌套循环中,可以给循环加标签来精确指定 break 和 continue 的目标:
'search: for room in &hotel {
for spot in &room.spots {
if spot.id == target {
println!("Found in room {}", room.number);
break 'search; // 退出外层循环
}
}
}标签同样支持 break 带值:
let sqrt = 'outer: loop {
for i in 0.. {
if i * i > n {
break 'outer i; // 退出 loop 并返回 i
}
}
};return 表达式
return 退出当前函数并返回值:
fn find_index(items: &[i32], target: i32) -> Option<usize> {
for (i, &item) in items.iter().enumerate() {
if item == target {
return Some(i); // 提前返回
}
}
None // 函数的尾表达式——隐式返回
}不写 return 时,函数体作为一个块,其尾表达式的值就是返回值。裸 return 等价于 return ()。
? 运算符的展开就用到 return:
// let val = expr?; 等价于
let val = match expr {
Ok(v) => v,
Err(e) => return Err(e.into()),
};!:永不返回的类型
有些表达式永远不会正常完成——它们要么发散(diverge),要么无限循环,要么终止程序。这些表达式的类型是 !(never 类型):
// 这些表达式的类型都是 !
fn exit_program(code: i32) -> ! {
std::process::exit(code);
}
fn serve_forever() -> ! {
loop {
// 无限循环,永不返回
}
}
// panic! 的类型也是 !
fn give_up() -> ! {
panic!("unrecoverable error");
}! 可以强制转换为任何类型——这使它在类型检查中非常灵活:
// 这个 match 的类型是 i32
let x: i32 = match some_value {
Some(v) => v,
None => panic!("no value"), // panic! 的类型是 !,可以强行转换为 i32
};loop 之所以作为一个独立的循环形式存在,正是因为 ! 与流程敏感分析(flow-sensitive analysis)的配合:
// while true 不能证明它是无限的(条件可能为 false)
// 但 loop 的逻辑由编译器假定为永不退出
let x = loop { /* 不返回 */ };
// x 的类型被推断为 !运算符概览
Rust 的运算符按优先级从高到低排列:
| 优先级 | 运算符 | 关联性 | 说明 |
|---|---|---|---|
| 最高 | [] () . | — | 索引、调用、字段 |
? | — | 错误传播 | |
- ! * & &mut | — | 一元运算符 | |
as | 左 | 类型转换 | |
* / % | 左 | 乘除取余 | |
+ - | 左 | 加减 | |
<< >> | 左 | 位移 | |
& | 左 | 按位与 | |
^ | 左 | 按位异或 | |
| | 左 | 按位或 | |
== != < <= > >= | 不可链 | 比较 | |
&& | 左 | 逻辑与(短路) | |
|| | 左 | 逻辑或(短路) | |
.. ..= | — | 范围 | |
= += -= *= 等 | — | 赋值 | |
| 最低 | |x| expr | — | 闭包 |
关键细节:
// 算术运算:溢出在 debug 模式下 panic,release 模式下回绕
let sum = a + b;
// 整数除法截断向零
assert_eq!(7 / 3, 2);
assert_eq!(-7 / 3, -2);
// 取余对浮点数也适用
assert!((12.34 % 5.0 - 2.34).abs() < 0.001);
// 按位非使用 !(不是 ~)
let hi: u8 = 0xe0;
let lo = !hi; // 0x1f
// 逻辑运算符是短路的,操作数必须是 bool
if enabled && check_valid() { // check_valid() 只在 enabled 为 true 时调用
// ...
}
// Rust 没有 ++ 和 -- 运算符
let mut x = 0;
x += 1; // 而不是 x++赋值
Rust 的赋值不链式,也不产生值:
let mut a = 1;
let mut b = 2;
// a = b = 3; // 错误:赋值不返回值
// 复合赋值
a += 1; // a = a + 1
b *= 2; // b = b * 2对于非 Copy 类型,赋值是移动:
let s1 = String::from("hello");
let mut s2 = String::from("world");
s2 = s1; // s1 移动到 s2
// println!("{}", s1); // 错误:s1 已被移动类型转换
使用 as 运算符进行显式类型转换:
let x: i32 = 42;
let y = x as u64; // 整数 → 整数
let z = x as f64; // 整数 → 浮点
// u8 可以 as char(0-255 是有效的 Unicode)
let c = 65u8 as char; // 'A'
// 指针转换(需要 unsafe)
// let p = &x as *const i32;Rust 的 as 不允许跨类型族的不安全转换(如整数到布尔值)。所有允许的转换见下表:
| 源类型 | 目标类型 |
|---|---|
| 任何整数 | 任何整数 |
| 任何整数 | f32 / f64 |
f32 / f64 | 任何整数 |
bool / char / C 风格 enum | 任何整数 |
u8 | char |
*T | *U(U 是任何类型) |
自动解引用强制转换(Deref Coercions)
Rust 在特定上下文对某些类型自动执行转换:
// &String → &str
fn greet(name: &str) {
println!("Hello, {}!", name);
}
let name = String::from("Alice");
greet(&name); // &String 自动转换为 &str
// &Vec<i32> → &[i32]
fn sum(values: &[i32]) -> i32 {
values.iter().sum()
}
let v = vec![1, 2, 3];
println!("{}", sum(&v)); // &Vec<i32> 自动转换为 &[i32]
// &Box<T> → &T
let b = Box::new(42);
let r: &i32 = &b; // &Box<i32> 自动转换为 &i32这些转换通过 Deref Trait 实现——自定义智能指针类型可以通过实现 Deref 来获得相同的自动解引用行为。
闭包
闭包是轻量级的匿名函数——它们可以捕获其所在作用域中的变量:
let factor = 2;
// 闭包捕获了 factor
let double = |x| x * factor;
assert_eq!(double(5), 10);
assert_eq!(double(10), 20);闭包的类型由编译器推断。如果需要显式标注类型:
let double = |x: i64| -> i64 { x * 2 };闭包的完整语法:
|参数1: 类型1, 参数2: 类型2| -> 返回类型 { 表达式或块 }当闭包体是单个表达式时可以省略 {};类型通常可以省略。
闭包与函数的根本区别在于:
- 函数
fn不能捕获外部变量(除非作为参数传入)。 - 闭包
|...|可以捕获其定义处的环境变量。
闭包的捕获方式(按值、按引用、按可变引用)将在后续章节深入讨论。
表达式在实践中的威力
理解「一切皆表达式」对 Rust 编程风格的影响是深远的。看一个实际例子——用表达式风格做 pixels 计算:
fn pixel_color(x: u32, y: u32, width: u32) -> Color {
let edge = match (x, y) {
(0, _) | (_, 0) | (w, _) if w == width - 1 => EdgeType::Border,
_ => EdgeType::Interior,
};
// if 作为表达式直接传入参数
set_pixel(x, y, if edge == EdgeType::Border {
Color::Black
} else {
let intensity = compute_intensity(x, y);
Color::grayscale(intensity)
});
}没有提前声明未初始化的 color 变量,没有赋值语句分散在各处——值从表达式流向使用点。
小结
- Rust 是表达式语言:
if、match、loop、块都产生值。分号;将表达式转为语句(丢弃值,返回())。 - 块的值是最后一个表达式(无分号)。
let支持遮蔽——同名变量依次精炼数据。 match要求穷尽所有可能模式,所有分支必须产生相同类型。if let是单模式匹配的语法糖。- 四种循环各有分工:
for用于迭代,while用于条件循环,while let用于模式匹配循环,loop用于无限循环且支持带值break。 !类型表示永不返回的表达式(panic!、loop {}、std::process::exit),可强制转换为任何类型。- 无
++/--,无链式赋值,算术默认检查溢出。as做显式类型转换,Deref自动转换让智能指针透明。 - 闭包
|x| expr是轻量级匿名函数,可捕获环境变量。与fn的核心区别在于捕获能力。