Skip to content
Published at:

06. Expressions — 表达式

Rust 是一门表达式语言(expression language)。这个特性来自 Lisp 的传统:几乎所有语法结构都产生值。在 C 语言中,ifwhile 是语句——它们不返回值,你只能用三元运算符 ?: 做条件求值。在 Rust 中,ifmatchloop 都产生值——三元运算符变得多余。

这对代码风格有深远影响:你可以将控制流嵌入到值的使用点,无需提前声明未初始化的变量。

本章系统介绍 Rust 的表达式体系——从块和分号,到控制流表达式,再到运算符和闭包。

表达式 vs 语句

在 Rust 中,几乎所有的东西都是表达式。表达式产生值;语句不产生值(或产生 ())。

rust
// 语句:以分号结尾
let x = 5;          // let 语句
println!("hello");  // 表达式语句(表达式的值被丢弃)

// 表达式:不以分号结尾(作为块的尾表达式时)
x + 1               // 算术表达式,产生值
if x > 0 { 1 } else { -1 }  // if 表达式,产生值

分号 ; 的作用是将表达式转换为语句——它丢弃表达式的值并返回单元类型 ()

rust
let x = {
    let y = 10;
    y + 1    // 没有分号:块的值为 11
};
println!("{}", x);  // 11

let z = {
    let y = 10;
    y + 1;   // 有分号:块的值为 ()
};
// println!("{}", z);  // 错误:z 是 (),不能这样打印

块表达式

{ ... } 是最基本的复合表达式。它的值是最后一个表达式的值(如果最后一个表达式以分号结尾则是 ()):

rust
let msg = {
    // 一系列语句
    let part1 = "hello";
    let part2 = "world";
    // 最后一个表达式——没有分号——是块的值
    format!("{} {}", part1, part2)
};
println!("{}", msg);  // "hello world"

块中还可以包含条目声明(item declarations)——fnstructuse 等。这些声明不产生值,但可以在块的作用域内使用:

rust
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 声明的完整语法:

rust
let name: Type = expr;

类型标注和初始值都可以省略(但不能同时省略):

rust
let x;           // 未初始化——后续必须赋值
x = 10;
let y = 20;      // 类型推断
let z: i32 = 30; // 完整形式

**遮蔽(shadowing)**允许用同名变量重新绑定:

rust
let line = read_line();           // line: String
let line = line.trim();          // line: &str(新的绑定,遮蔽旧的)
let line = line.parse::<i32>()?; // line: i32

这种模式在处理管道式数据转换时非常自然——每个步骤产生一个更精炼的值,旧值被遮蔽。

ifmatch 表达式

if 表达式

if 产生值。条件必须是 bool(不允许隐式转换),不要求括号,但大括号是必须的:

rust
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 的所有分支必须产生相同类型。没有 elseif 等价于带有空 else 块的 if——返回 ()

rust
// 这个 if 返回 ()——因为没有 else 分支
if condition {
    do_something();
}
// 等价于
if condition {
    do_something();
} else {
    // 隐式的 else { }
}

match 表达式

match 是 Rust 中最强大的控制流结构——对值进行模式匹配,要求穷尽所有可能:

rust
match code {
    0 => println!("success"),
    1 => println!("pending"),
    2 => println!("error"),
    _ => println!("unknown"),  // 通配符捕获所有剩余情况
}

match 也产生值:

rust
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

所有分支必须产生相同类型:

rust
// 编译错误:分支类型不一致
// let val = match status {
//     Status::Active => 1,
//     Status::Inactive => "inactive",  // 错误:期望 i32,得到 &str
// };

if let

if let 是仅匹配一个模式加一个通配符的 match 的语法糖:

rust
// 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");
}

常见用例:

rust
// 检查 Result
if let Err(e) = do_something() {
    eprintln!("Error: {}", e);
}

// 解析可选参数
if let Some(name) = params.get("name") {
    println!("Hello, {}!", name);
}

循环表达式

Rust 有四种循环形式:

循环形式语法用途
whilewhile condition { }条件循环,条件必须是 bool
while letwhile let pattern = expr { }模式匹配循环
looploop { }无限循环(可带 break 值)
forfor pattern in iterable { }迭代循环

for 循环

for 是最常用的循环形式,用于遍历任何实现了 IntoIterator 的类型:

rust
// 遍历范围
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!"]

loopbreak

loop 是唯一可以从中 break 带值的循环:

rust
// 用 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 中退出。whilefor 不支持带值的 break,因为循环体可能一次都不执行。

while let

rust
// 用 while let 处理迭代器
let mut v = vec![1, 2, 3];
while let Some(x) = v.pop() {
    println!("{}", x);  // 3, 2, 1
}

循环标签

在嵌套循环中,可以给循环加标签来精确指定 breakcontinue 的目标:

rust
'search: for room in &hotel {
    for spot in &room.spots {
        if spot.id == target {
            println!("Found in room {}", room.number);
            break 'search;  // 退出外层循环
        }
    }
}

标签同样支持 break 带值:

rust
let sqrt = 'outer: loop {
    for i in 0.. {
        if i * i > n {
            break 'outer i;  // 退出 loop 并返回 i
        }
    }
};

return 表达式

return 退出当前函数并返回值:

rust
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

rust
// let val = expr?; 等价于
let val = match expr {
    Ok(v) => v,
    Err(e) => return Err(e.into()),
};

!:永不返回的类型

有些表达式永远不会正常完成——它们要么发散(diverge),要么无限循环,要么终止程序。这些表达式的类型是 !(never 类型):

rust
// 这些表达式的类型都是 !
fn exit_program(code: i32) -> ! {
    std::process::exit(code);
}

fn serve_forever() -> ! {
    loop {
        // 无限循环,永不返回
    }
}

// panic! 的类型也是 !
fn give_up() -> ! {
    panic!("unrecoverable error");
}

! 可以强制转换为任何类型——这使它在类型检查中非常灵活:

rust
// 这个 match 的类型是 i32
let x: i32 = match some_value {
    Some(v) => v,
    None => panic!("no value"),  // panic! 的类型是 !,可以强行转换为 i32
};

loop 之所以作为一个独立的循环形式存在,正是因为 ! 与流程敏感分析(flow-sensitive analysis)的配合:

rust
// while true 不能证明它是无限的(条件可能为 false)
// 但 loop 的逻辑由编译器假定为永不退出
let x = loop { /* 不返回 */ };
// x 的类型被推断为 !

运算符概览

Rust 的运算符按优先级从高到低排列:

优先级运算符关联性说明
最高[] () .索引、调用、字段
?错误传播
- ! * & &mut一元运算符
as类型转换
* / %乘除取余
+ -加减
<< >>位移
&按位与
^按位异或
|按位或
== != < <= > >=不可链比较
&&逻辑与(短路)
||逻辑或(短路)
.. ..=范围
= += -= *=赋值
最低|x| expr闭包

关键细节:

rust
// 算术运算:溢出在 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 的赋值不链式,也不产生值:

rust
let mut a = 1;
let mut b = 2;
// a = b = 3;  // 错误:赋值不返回值

// 复合赋值
a += 1;   // a = a + 1
b *= 2;   // b = b * 2

对于非 Copy 类型,赋值是移动:

rust
let s1 = String::from("hello");
let mut s2 = String::from("world");
s2 = s1;  // s1 移动到 s2
// println!("{}", s1);  // 错误:s1 已被移动

类型转换

使用 as 运算符进行显式类型转换:

rust
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任何整数
u8char
*T*UU 是任何类型)

自动解引用强制转换(Deref Coercions)

Rust 在特定上下文对某些类型自动执行转换:

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 来获得相同的自动解引用行为。

闭包

闭包是轻量级的匿名函数——它们可以捕获其所在作用域中的变量:

rust
let factor = 2;

// 闭包捕获了 factor
let double = |x| x * factor;

assert_eq!(double(5), 10);
assert_eq!(double(10), 20);

闭包的类型由编译器推断。如果需要显式标注类型:

rust
let double = |x: i64| -> i64 { x * 2 };

闭包的完整语法:

|参数1: 类型1, 参数2: 类型2| -> 返回类型 { 表达式或块 }

当闭包体是单个表达式时可以省略 {};类型通常可以省略。

闭包与函数的根本区别在于:

  • 函数 fn 不能捕获外部变量(除非作为参数传入)。
  • 闭包 |...| 可以捕获其定义处的环境变量。

闭包的捕获方式(按值、按引用、按可变引用)将在后续章节深入讨论。

表达式在实践中的威力

理解「一切皆表达式」对 Rust 编程风格的影响是深远的。看一个实际例子——用表达式风格做 pixels 计算:

rust
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 是表达式语言ifmatchloop、块都产生值。分号 ; 将表达式转为语句(丢弃值,返回 ())。
  • 块的值是最后一个表达式(无分号)。let 支持遮蔽——同名变量依次精炼数据。
  • match 要求穷尽所有可能模式,所有分支必须产生相同类型。if let 是单模式匹配的语法糖。
  • 四种循环各有分工:for 用于迭代,while 用于条件循环,while let 用于模式匹配循环,loop 用于无限循环且支持带值 break
  • ! 类型表示永不返回的表达式(panic!loop {}std::process::exit),可强制转换为任何类型。
  • ++/--,无链式赋值,算术默认检查溢出。as 做显式类型转换,Deref 自动转换让智能指针透明。
  • 闭包 |x| expr 是轻量级匿名函数,可捕获环境变量。与 fn 的核心区别在于捕获能力。