
枚举:一个变量的"多重人格",match 来拆分
假设你要表示一个"消息"类型,它可能是:
在其他语言里,你可能得这样写:
// JavaScript... 自求多福
let message = {
type: "text",
content: "Hello",
// 或者
type: "image",
url: "xxx.jpg",
// 或者... 乱成一锅粥
};
但在 Rust 里,用**枚举(Enum)**就能优雅地表示:
enum Message {
Text(String),
Image(String),
Voice(u32),
Video(String, u32),
}
每个变体可以有不同的数据!而且编译器会确保你处理所有情况。
今天咱们就来聊聊 Rust 的枚举和模式匹配,这可是 Rust 的杀手锏之一!
枚举(Enum)是自定义类型,它有多种可能的"变体"(Variant)。
生活化类比:
enum 关键字enum Direction {
North,
South,
East,
West,
}
最简单的枚举,每个变体不带数据。
enum Message {
Quit, // 无数据
Move { x: i32, y: i32 }, // 命名字段
Write(String), // 单个值
ChangeColor(i32, i32, i32), // 多个值
}
重点:每个变体可以有不同的数据结构!
let m1 = Message::Quit;
let m2 = Message::Move { x: , y: };
let m3 = Message::Write(String::from("Hello"));
let m4 = Message::ChangeColor(, , );
注意:用 EnumName::Variant 语法。
match:模式匹配的核心match 让你根据枚举的变体执行不同代码:
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => ,
Coin::Nickel => ,
Coin::Dime => ,
Coin::Quarter => ,
}
}
重点:match 是表达式,有返回值!而且必须穷尽所有可能。
enum Message {
Quit,
Write(String),
}
fn handle_message(msg: Message) {
match msg {
Message::Quit => println!("退出"),
Message::Write(text) => println!("文本:{}", text),
}
}
text 是从 Write 变体里"提取"出来的。这叫模式匹配解构。
if let:简化版 match如果你只关心一个变体,其他情况不用处理,可以用 if let:
let msg = Message::Write(String::from("Hello"));
// 完整 match
match msg {
Message::Write(text) => println!("{}", text),
_ => (), // 其他情况啥也不干
}
// 简化 if let
if let Message::Write(text) = msg {
println!("{}", text);
}
Option 枚举:Rust 的"空值"处理Rust 没有 null!它用 Option 枚举表示"可能有值,也可能没有":
enum Option<T> {
Some(T),
None,
}
用法:
let some_number: Option<i32> = Some();
let no_number: Option<i32> = None;
// 处理 Option
match some_number {
Some(n) => println!("数字是:{}", n),
None => println!("没有数字"),
}
为什么好? 编译器会强制你处理 None 情况,不会有空指针异常!
模式匹配可以用于:
_let point = Point { x: , y: };
match point {
Point { x, y: } => println!("在 X 轴上,x={}", x),
Point { x: , y } => println!("在 Y 轴上,y={}", y),
Point { x, y } => println!("x={}, y={}", x, y),
}
#[derive(Debug)]
enum TrafficLight {
Red,
Yellow,
Green,
}
fn can_pass(light: TrafficLight) -> bool {
match light {
TrafficLight::Red => false,
TrafficLight::Yellow => false,
TrafficLight::Green => true,
}
}
fn action(light: TrafficLight) -> &str {
match light {
TrafficLight::Red => "停车",
TrafficLight::Yellow => "等待",
TrafficLight::Green => "通行",
}
}
fn main() {
let light = TrafficLight::Green;
println!("可以通行吗?{}", can_pass(light));
println!("动作:{}", action(light));
}
enum Color {
Red,
Green,
Blue,
}
fn describe(color: Color) -> &str {
match color {
Color::Red => "红色",
Color::Green => "绿色",
// 忘记 Blue
}
}
编译器错误:
error[E0004]: non-exhaustive patterns: `Color::Blue` not covered
--> src/main.rs:5:11
|
5 | match color {
| ^^^^^ pattern `Color::Blue` not covered
|
= note: `Color` defined as enum with 3 variants
help: ensure that all possible cases are being handled
|
5 ~ match color {
6 | Color::Red => "红色",
7 | Color::Green => "绿色",
8 ~ Color::Blue => todo!(),
9 ~ }
|
人话翻译:编译器:"Blue 怎么办?你想让我猜吗?把所有情况都处理了!"
修复:加上 Blue,或者用 _ 通配符。
match color {
Color::Red => "红色",
Color::Green => "绿色",
Color::Blue => "蓝色",
}
// 或者
match color {
Color::Red => "红色",
_ => "其他颜色",
}
enum Message {
Text(String),
Number(i32),
}
fn handle(msg: Message) {
match msg {
Message::Text(n) => println!("{}", n), // n 是 String,不是 i32
Message::Number(s) => println!("{}", s), // s 是 i32,不是 String
}
}
编译器错误:
error[E0277]: `String` doesn't implement `std::fmt::Display`
--> src/main.rs:7:38
|
7 | Message::Text(n) => println!("{}", n),
| ^^ `String` cannot be formatted
人话翻译:编译器:"你这里的变量类型搞反了!Text 里是 String,Number 里是 i32,别乱来!"
Option 不处理 Nonefn get_first(vec: Vec<i32>) -> i32 {
match vec.first() {
Some(n) => *n,
// 忘记 None
}
}
编译器错误:
error[E0004]: non-exhaustive patterns: `None` not covered
--> src/main.rs:3:11
|
3 | match vec.first() {
| ^^^^^^^^^^^ pattern `None` not covered
人话翻译:编译器:"空数组怎么办?返回啥?想清楚!"
修复:
fn get_first(vec: Vec<i32>) -> Option<i32> {
match vec.first() {
Some(n) => Some(*n),
None => None,
}
}
_ 通配符当你不关心某些变体时,用 _:
match msg {
Message::Text(t) => println!("{}", t),
_ => (), // 其他情况啥也不干
}
Option 直接解包let opt: Option<i32> = Some();
let n = opt.unwrap(); // 如果是 None,会 panic!
推荐做法:
// 用 match
match opt {
Some(n) => println!("{}", n),
None => println!("没有值"),
}
// 用 if let
if let Some(n) = opt {
println!("{}", n);
}
// 用 unwrap_or
let n = opt.unwrap_or(); // None 时返回 0
let msg = Message::Text(String::from("Hello"));
match msg {
Message::Text(text) => println!("{}", text),
_ => (),
}
println!("{:?}", msg); // 错误:msg 已被移动
修复:用借用。
match &msg {
Message::Text(text) => println!("{}", text),
_ => (),
}
if let 滥用if let 只适用于只关心一个变体的情况。如果要处理多个,还是用 match。
#[derive(Debug, PartialEq)]
enum OrderState {
Pending,
Processing,
Shipped,
Delivered,
Cancelled,
}
impl OrderState {
fn next(self) -> Option<Self> {
match self {
OrderState::Pending => Some(OrderState::Processing),
OrderState::Processing => Some(OrderState::Shipped),
OrderState::Shipped => Some(OrderState::Delivered),
OrderState::Delivered | OrderState::Cancelled => None,
}
}
fn can_cancel(&self) -> bool {
match self {
OrderState::Pending | OrderState::Processing => true,
_ => false,
}
}
}
fn main() {
let mut state = OrderState::Pending;
println!("初始状态:{:?}", state);
println!("可以取消吗?{}", state.can_cancel());
state = state.next().unwrap();
println!("下一状态:{:?}", state);
}
Option 处理查找fn find_user(users: &[(u32, &str)], id: u32) -> Option<&str> {
for &(uid, name) in users {
if uid == id {
return Some(name);
}
}
None
}
fn main() {
let users = [
(, "Alice"),
(, "Bob"),
(, "Charlie"),
];
match find_user(&users, ) {
Some(name) => println!("找到用户:{}", name),
None => println!("用户不存在"),
}
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(State),
}
enum State {
Alabama,
Alaska,
Arizona,
}
fn value(coin: &Coin) -> u32 {
match coin {
Coin::Penny => ,
Coin::Nickel => ,
Coin::Dime => ,
Coin::Quarter(state) => match state {
State::Alabama => ,
State::Alaska => ,
State::Arizona => ,
},
}
}
// 或者用守卫
fn describe(coin: &Coin) -> &str {
match coin {
Coin::Penny => "便士",
Coin::Nickel => "镍币",
Coin::Dime => "角币",
Coin::Quarter(State::Alabama) => "阿拉巴马州 25 分币",
Coin::Quarter(_) => "其他州 25 分币",
}
}

match 必须穷尽所有可能,编译器会帮你检查Option<T> 替代 null,Some(T) 有值,None 无值if let 是简化版 match,只关心一个变体时用下篇预告:枚举里存数据,那数据存在哪?直接复制还是借用?下一篇聊聊借用与引用,彻底搞懂 Rust 的内存魔法!