C 程序员第一次看到 match,很容易把它当成更严格的 switch。
这个类比能帮你入门,但不够。C 的 switch 主要按整数值分支;如果状态背后还有数据,你还要手工从结构体或 union 里取字段。
Rust 的 match 做的是模式匹配:
判断值长什么样,同时把里面的数据取出来。
前面讲 enum 时已经看到,Rust 可以把 C 的 tagged union 写成携带数据的 enum。这一篇继续往下看:match 怎样处理更复杂的状态、引用、条件和局部解包。
match 同时分支和解包
C tagged union 常见写法是先看 tag,再读 payload:
switch (token->kind) {
case TOKEN_INT:
printf("%d\n", token->data.int_value);
break;
case TOKEN_IDENT:
printf("%s\n", token->data.ident);
break;
}
Rust 里分支和解包在同一个模式里完成:
enum Token {
Int(i32),
Ident(String),
End,
}
fn describe(token: &Token) {
match token {
Token::Int(value) => println!("int {value}"),
Token::Ident(name) => println!("ident {name}"),
Token::End => println!("end"),
}
}
Token::Int(value) 不只是判断“这是 Int”。它还把里面的 i32 绑定到 value。
这个模型比 switch 更接近:
如果值符合这个形状,就进入这个分支,并把形状里的局部变量取出来。
结构体和 tuple 也能解包
模式不只用于 enum。结构体也可以匹配字段:
struct Header {
version: u8,
flags: u8,
len: u16,
}
fn print_header(header: Header) {
match header {
Header { version, flags, len } => {
println!("v={version} flags={flags} len={len}");
}
}
}
如果只关心部分字段,可以用 .. 忽略剩余字段:
fn is_ack(header: &Header) -> bool {
match header {
Header { flags, .. } => (*flags & 0x01) != 0,
}
}
tuple 也一样:
let point = (10, 20);
match point {
(0, y) => println!("on y axis: {y}"),
(x, 0) => println!("on x axis: {x}"),
(x, y) => println!("point {x}, {y}"),
}
C 里这些通常是 if 加字段访问。Rust 的模式把“判断形状”和“取字段”合在了一起。
匹配引用时要分清借用和移动
对拥有数据的 enum,直接按值 match 可能会移动里面的数据:
enum Message {
Text(String),
Ping,
}
fn consume(msg: Message) {
match msg {
Message::Text(text) => println!("{text}"),
Message::Ping => println!("ping"),
}
}
这里 text 得到的是 String 的所有权。consume 本来就接收 Message,所以没问题。
如果只是查看消息,不想拿走所有权,就匹配引用:
fn inspect(msg: &Message) {
match msg {
Message::Text(text) => println!("{text}"),
Message::Ping => println!("ping"),
}
}
这里 text 的类型是 &String,因为 msg 本身是 &Message。常见场景下,编译器会自动处理引用模式,不需要到处写 ref。
对 C 程序员来说,可以先这样理解:
match value -> 可能移动 value 里的数据
match &value -> 只是借用并查看
写库 API 时,如果函数只是读状态,参数通常接收引用。
if let 处理单个关心的分支
如果只关心一个变体,用完整 match 会显得重:
match token {
Token::Ident(name) => println!("{name}"),
_ => {}
}
Rust 提供 if let:
if let Token::Ident(name) = token {
println!("{name}");
}
它的含义是:如果 token 符合 Token::Ident(name) 这个模式,就执行代码并绑定 name。
处理 Option 很常见:
if let Some(index) = find_index(items, target) {
println!("found at {index}");
}
这比 C 里用 -1 特殊值再判断更明确:
int index = find_index(items, len, target);
if (index >= 0) {
printf("%d\n", index);
}
如果两个分支都重要,用 match;如果只关心一个成功形状,用 if let。
while let 处理重复取值
C 里常见循环是“不断取下一个元素,直到没有”:
while (next_token(&token)) {
handle(token);
}
Rust 中如果接口返回 Option<T>,可以用 while let:
while let Some(token) = lexer.next_token() {
handle(token);
}
这表示:只要返回值匹配 Some(token),就继续循环;遇到 None 自动结束。
对 Vec 的 pop 也一样:
let mut stack = vec![1, 2, 3];
while let Some(value) = stack.pop() {
println!("{value}");
}
这比手写 loop 加 break 更贴近数据形状。
匹配守卫表达额外条件
有时模式本身还不够,还要加条件。Rust 用匹配守卫:
match token {
Token::Int(value) if *value > 0 => println!("positive {value}"),
Token::Int(value) => println!("non-positive {value}"),
Token::Ident(name) if name.len() > 16 => println!("long ident"),
Token::Ident(name) => println!("ident {name}"),
Token::End => println!("end"),
}
if *value > 0 是这个分支的额外条件。只有模式匹配并且条件为真,才进入这个分支。
匹配守卫适合表达“同一种形状下再按条件细分”。如果条件越来越复杂,通常应该先计算一个中间值,或者把判断放进函数里。
_ 会吞掉未来分支
_ 表示“其他所有情况”:
match token {
Token::Int(value) => println!("{value}"),
_ => println!("other"),
}
它很方便,但在协议、状态机、命令处理这类代码里要谨慎。_ 会让新增变体不再触发编译器提醒。
如果状态有明确工程意义,优先写全:
match token {
Token::Int(value) => println!("{value}"),
Token::Ident(name) => println!("{name}"),
Token::End => println!("end"),
}
新增 Token::Error 时,所有需要处理新状态的地方都会编译失败。这是 match 比 switch 更有价值的部分。
如果确实只想忽略某些值,可以把忽略写得更窄:
match token {
Token::Int(value) => println!("{value}"),
Token::Ident(_) => println!("identifier"),
Token::End => {}
}
这样比一个大 _ 更不容易掩盖未来变化。
matches! 适合简单判断
如果只想得到一个布尔值,可以用 matches!:
let is_end = matches!(token, Token::End);
也可以带守卫:
let is_large = matches!(token, Token::Int(value) if value > 1024);
这适合替代简单的 tag 判断。不要把复杂业务逻辑硬塞进 matches!,需要解包和处理多分支时,match 更清楚。
这一篇先记住什么
match 不是 C switch 的语法升级,而是模式匹配:
按值的形状分支
在分支里解包数据
让编译器检查是否覆盖所有情况
用 if let / while let 表达单分支和循环取值
用守卫表达额外条件
谨慎使用 _
从 C 迁移到 Rust 时,不要只把 switch 翻译成 match。更重要的是把“tag + payload + 手工字段读取”改成“enum + 模式匹配”,让状态和数据的关系进入类型系统。
下一篇讲 iterator。C 里习惯用索引和指针移动遍历数据,Rust 会把很多遍历逻辑变成可组合的值流。