Skip to content

枚举与模式匹配

学习内容: 带数据的 Rust 枚举 vs Python Union 类型、穷尽式 match vs 非穷尽式 match/case、 Option<T> 作为 None 的编译期替代品,以及卫语句模式。

难度: 🟡 中级

Python 3.10 引入了 match 语句和类型联合。Rust 的枚举走得更远——每个变体可以携带不同的数据, 而且编译器确保你处理了所有情况。

# Python 3.10+ — 结构化模式匹配
from typing import Union
from dataclasses import dataclass
@dataclass
class Circle:
radius: float
@dataclass
class Rectangle:
width: float
height: float
@dataclass
class Triangle:
base: float
height: float
Shape = Union[Circle, Rectangle, Triangle] # 类型别名
def area(shape: Shape) -> float:
match shape:
case Circle(radius=r):
return 3.14159 * r * r
case Rectangle(width=w, height=h):
return w * h
case Triangle(base=b, height=h):
return 0.5 * b * h
# 漏掉分支也不会有编译器警告!
# 添加新形状?只能 grep 代码库,希望找到所有 match 块。
// Rust — 枚举变体携带数据,编译器强制穷尽匹配
enum Shape {
Circle(f64), // Circle 携带半径
Rectangle(f64, f64), // Rectangle 携带宽度和高度
Triangle { base: f64, height: f64 }, // 命名字段也可以
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle { base, height } => 0.5 * base * height,
// 如果你添加了 Shape::Pentagon 但忘记处理它,
// 编译器会拒绝构建。无需 grep。
}
}

核心洞见:Rust 的 match 是穷尽式的——编译器验证你处理了每个变体。 给枚举添加新变体后,编译器会精确告知哪些 match 块需要更新。 Python 的 match 没有这种保证。

# Python — 多种被 Rust 枚举替代的模式:
# 1. 字符串常量
STATUS_PENDING = "pending"
STATUS_ACTIVE = "active"
STATUS_CLOSED = "closed"
# 2. Python Enum(无数据)
from enum import Enum
class Status(Enum):
PENDING = "pending"
ACTIVE = "active"
CLOSED = "closed"
# 3. 标记联合(类 + 类型字段)
class Message:
def __init__(self, kind, **data):
self.kind = kind
self.data = data
# Message(kind="text", content="hello")
# Message(kind="image", url="...", width=100)
// Rust — 一个枚举实现所有三种模式,甚至更多
// 1. 简单枚举(类似 Python 的 Enum)
enum Status {
Pending,
Active,
Closed,
}
// 2. 带数据的枚举(标记联合——类型安全!)
enum Message {
Text(String),
Image { url: String, width: u32, height: u32 },
Quit, // 无数据
Move { x: i32, y: i32 },
}
flowchart TD
E["enum Message"] --> T["Text(String)<br/>🏷️ tag=0 + String data"]
E --> I["Image { url, width, height }<br/>🏷️ tag=1 + 3 fields"]
E --> Q["Quit<br/>🏷️ tag=2 + no data"]
E --> M["Move { x, y }<br/>🏷️ tag=3 + 2 fields"]
style E fill:#d4edda,stroke:#28a745
style T fill:#fff3cd
style I fill:#fff3cd
style Q fill:#fff3cd
style M fill:#fff3cd

内存洞察:Rust 枚举是”标记联合”——编译器存储一个判别标签 + 足够容纳最大变体的空间。 Python 的等价物(Union[str, dict, None])没有紧凑的表示方式。

另请参阅:第 9 章 — 错误处理 大量使用枚举——Result<T, E> 和 Option<T> 只是带有 match 的枚举。

fn process(msg: &Message) {
match msg {
Message::Text(content) => println!("Text: {content}"),
Message::Image { url, width, height } => {
println!("Image: {url} ({width}x{height})")
}
Message::Quit => println!("Quitting"),
Message::Move { x, y } => println!("Moving to ({x}, {y})"),
}
}

# Python — 通配符是可选的,没有编译器帮助
def describe(value):
match value:
case 0:
return "zero"
case 1:
return "one"
# 如果忘记默认分支,Python 会静默返回 None。
# 没有警告,没有错误。
describe(42) # 返回 None——静默 bug
// Rust — 必须处理所有可能的情况
fn describe(value: i32) -> &'static str {
match value {
0 => "zero",
1 => "one",
// 编译错误:非穷尽模式:`i32::MIN..=-1_i32`
// 和 `2_i32..=i32::MAX` 未被覆盖
_ => "other", // _ = 捕获全部(对于开放类型是必需的)
}
}
// 对于枚举,不需要捕获全部——编译器知道所有变体:
enum Color { Red, Green, Blue }
fn color_hex(c: Color) -> &'static str {
match c {
Color::Red => "#ff0000",
Color::Green => "#00ff00",
Color::Blue => "#0000ff",
// 不需要 _ — 所有变体都被覆盖
// 之后添加 Color::Yellow → 编译器在这里报错
}
}
// 多个值(类似 Python 的 case 1 | 2 | 3:)
match value {
1 | 2 | 3 => println!("small"),
4..=9 => println!("medium"), // 范围模式
_ => println!("large"),
}
// 卫语句(类似 Python 的 case x if x > 0:)
match temperature {
t if t > 100 => println!("boiling"),
t if t < 0 => println!("freezing"),
t => println!("normal: {t}°"),
}
// 嵌套解构
let point = (3, (4, 5));
match point {
(0, _) => println!("on y-axis"),
(_, (0, _)) => println!("y=0"),
(x, (y, z)) => println!("x={x}, y={y}, z={z}"),
}

Option<T> 是对 Python 开发者最重要的 Rust 枚举。它用类型安全的替代品替换了 None。

# Python — None 是一个可以出现在任何地方的值
def find_user(user_id: int) -> dict | None:
users = {1: {"name": "Alice"}}
return users.get(user_id)
user = find_user(999)
# user 是 None — 但没有任何东西强制你检查!
print(user["name"]) # 运行时 TypeError
// Rust — Option<T> 强制你处理 None 的情况
fn find_user(user_id: i64) -> Option<User> {
let users = HashMap::from([(1, User { name: "Alice".into() })]);
users.get(&user_id).cloned()
}
let user = find_user(999);
// user 是 Option<User> — 不处理 None 就无法使用它
// 方法 1:match
match find_user(999) {
Some(user) => println!("Found: {}", user.name),
None => println!("Not found"),
}
// 方法 2:if let(类似 Python 的 if (x := expr) is not None)
if let Some(user) = find_user(1) {
println!("Found: {}", user.name);
}
// 方法 3:unwrap_or
let name = find_user(999)
.map(|u| u.name)
.unwrap_or_else(|| "Unknown".to_string());
// 方法 4:? 操作符(在返回 Option 的函数中)
fn get_user_name(id: i64) -> Option<String> {
let user = find_user(id)?; // 如果没找到,提前返回 None
Some(user.name)
}
模式PythonRust
检查是否存在if x is not None:if let Some(x) = opt {
默认值x or defaultopt.unwrap_or(default)
默认工厂x or compute()opt.unwrap_or_else(|| compute())
存在时转换f(x) if x else Noneopt.map(f)
链式查找x and x.attr and x.attr.method()opt.and_then(|x| x.method())
None 时崩溃无法防止opt.unwrap()(panic)或 opt.expect("msg")
获取或抛出x if x else raiseopt.ok_or(Error)?

练习:形状面积计算器(点击展开)

挑战:定义一个枚举 Shape,包含变体 Circle(f64)(半径)、Rectangle(f64, f64)(宽、高) 和 Triangle(f64, f64)(底、高)。使用 match 实现方法 fn area(&self) -> f64。 创建每种形状各一个并打印面积。

答案
use std::f64::consts::PI;
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Triangle(f64, f64),
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle(r) => PI * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle(b, h) => 0.5 * b * h,
}
}
}
fn main() {
let shapes = [
Shape::Circle(5.0),
Shape::Rectangle(4.0, 6.0),
Shape::Triangle(3.0, 8.0),
];
for shape in &shapes {
println!("Area: {:.2}", shape.area());
}
}

关键收获:Rust 枚举替代了 Python 的 Union[Circle, Rectangle, Triangle] + isinstance() 检查。 编译器确保你处理每个变体——添加新形状但不更新 area() 是编译错误。