引言
或模式(Or Patterns)是 Rust 1.53 版本引入的重要特性,通过 | 操作符允许在单个模式位置匹配多个可能的值。这个看似简单的语法糖,实际上深刻改变了模式匹配代码的组织方式,消除了大量重复的匹配分支,提升了代码的可维护性和可读性。或仅仅是语法上的便利,更体现了 Rust 在保持零成本抽象的同时,如何通过语言设计提升开发体验。理解或模式的语义、限制和最佳实践,是掌握现代 Rust 模式匹配的必要技能。本文将从语法细节、绑定规则、嵌套使用到实际应用,全面剖析这一强大特性。
或模式的基本语法
或模式使用 | 操作符连接多个模式,表示"匹配其中任意一个"。最简单的形式是 pattern1 | pattern2 | pattern3,当被匹配的值符合任何一个模式时,整个或模式就匹配成功。这种语法在枚举变体、字面量、范围等各种模式中都可以使用。
关键理解是,或模式中的每个子模式必须绑定相同的变量集合,且类型兼容。这个限制确保了无论匹配到哪个分支,后续代码都能统一处理绑定的变量。例如,Some(x) | None 是非法的,因为 None 不绑定 x;但 Some(x) | Some(y) 在某些情况下可以工作,如果 x 和 y 实际上是同一个绑定名。
或模式与变量绑定的约束
或模式最微妙的规则是变量绑定的一致性要求。当使用 | 连接多个模式时,所有分支必须绑定完全相同的变量集。这意味着 1 | 2 | 3 是合法的(都不绑定变量),Some(x) | Some(x) 看起来重复但合法(都绑定 x),但 Some(x) | None 不合法(绑定不一致)。
更复杂的情况涉及嵌套结构。在 Point { x, y: 0 } | Point { x, y: 1 } 中,两个分支都绑定了 x,因此合法。但如果写成 Point { x, y } | Point { x, z },即使 x 在两边都出现,由于 y 和 z 不同,这也是非法的。理解这些规则需要认识到:或模式是在匹配层面的"或",而不是绑定层面的"或"。
深度实践:或模式的全景应用
rust
// === 案例 1:基础或模式 ===
fn classify_digit(c: char) -> &'static str {
match c {
'0' | '1' => "binary digit",
'2' | '3' | '4' | '5' | '6' | '7' => "octal extra",
'8' | '9' => "decimal extra",
_ => "not a digit",
}
}
fn basic_or_pattern_demo() {
let chars = vec!['0', '5', '9', 'a'];
for c in chars {
println!("'{}': {}", c, classify_digit(c));
}
}
// === 案例 2:或模式与枚举 ===
#[derive(Debug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
fn handle_message(msg: Message) {
match msg {
Message::Quit | Message::Move { x: 0, y: 0 } => {
println!("No action or quit")
}
Message::Move { x, y } => {
println!("Move to ({}, {})", x, y)
}
Message::Write(text) | Message::ChangeColor(_, _, _) => {
println!("Visual change: {:?}", msg)
}
}
}
// === 案例 3:或模式与范围的组合 ===
fn classify_value(n: i32) -> &'static str {
match n {
1 | 2 | 3 => "small prime",
5 | 7 | 11 | 13 => "medium prime",
4 | 6 | 8 | 9 | 10 | 12 => "small composite",
-10..=-1 | 14..=20 => "other range",
_ => "unclassified",
}
}
fn or_with_range_demo() {
for n in vec![1, 5, 8, -5, 15, 100] {
println!("{}: {}", n, classify_value(n));
}
}
// === 案例 4:嵌套结构中的或模式 ===
#[derive(Debug)]
struct Config {
mode: Mode,
level: u8,
}
#[derive(Debug)]
enum Mode {
Debug,
Release,
Test,
}
fn validate_config(config: &Config) -> bool {
match config {
Config {
mode: Mode::Debug | Mode::Test,
level: 1..=3,
} => true,
Config {
mode: Mode::Release,
level: 0 | 1,
} => true,
_ => false,
}
}
fn nested_or_demo() {
let configs = vec![
Config {
mode: Mode::Debug,
level: 2,
},
Config {
mode: Mode::Release,
level: 1,
},
Config {
mode: Mode::Test,
level: 5,
},
];
for config in configs {
println!("{:?}: valid = {}", config, validate_config(&config));
}
}
// === 案例 5:或模式与 @ 绑定 ===
fn process_http_status(code: u16) {
match code {
success @ (200 | 201 | 204) => {
println!("Success code: {}", success)
}
redirect @ (301 | 302 | 307 | 308) => {
println!("Redirect code: {}", redirect)
}
client_err @ (400 | 401 | 403 | 404) => {
println!("Client error: {}", client_err)
}
server_err @ (500 | 502 | 503) => {
println!("Server error: {}", server_err)
}
_ => println!("Other code: {}", code),
}
}
fn or_with_at_binding_demo() {
for code in vec![200, 302, 404, 500] {
process_http_status(code);
}
}
// === 案例 6:或模式与 Option/Result ===
fn handle_optional(opt: Option<Result<i32, String>>) -> String {
match opt {
Some(Ok(value)) | Some(Err(_)) if false => {
unreachable!()
}
Some(Ok(value)) => format!("Success: {}", value),
Some(Err(e)) | None if false => unreachable!(),
Some(Err(e)) => format!("Error: {}", e),
None => String::from("No value"),
}
}
// === 案例 7:元组中的或模式 ===
fn analyze_point(point: (i32, i32)) -> &'static str {
match point {
(0, 0) => "origin",
(0, _) | (_, 0) => "on axis",
(x, y) if x == y => "diagonal",
(x, y) if x == -y => "anti-diagonal",
(-100..=100, -100..=100) => "in bounds",
_ => "out of bounds",
}
}
fn tuple_or_pattern_demo() {
let points = vec![(0, 0), (0, 5), (3, 3), (50, 50), (200, 200)];
for point in points {
println!("{:?}: {}", point, analyze_point(point));
}
}
// === 案例 8:或模式消除代码重复 ===
// 旧写法:重复的分支
fn old_style_match(value: i32) -> &'static str {
match value {
1 => "one",
2 => "two",
3 => "three",
4 => "four",
5 => "five",
_ => "other",
}
}
// 新写法:使用或模式
fn new_style_match(value: i32) -> &'static str {
match value {
1 | 2 | 3 | 4 | 5 => "single digit",
_ => "other",
}
}
// === 案例 9:复杂的业务逻辑 ===
#[derive(Debug)]
enum PaymentMethod {
Cash,
CreditCard { last4: String },
DebitCard { last4: String },
MobilePay { provider: String },
}
fn calculate_fee(method: &PaymentMethod, amount: f64) -> f64 {
match method {
PaymentMethod::Cash => 0.0,
PaymentMethod::CreditCard { .. } | PaymentMethod::DebitCard { .. } => {
amount * 0.029 // 2.9% 手续费
}
PaymentMethod::MobilePay { provider }
if provider == "Alipay" || provider == "WeChat" => {
amount * 0.006 // 0.6% 手续费
}
PaymentMethod::MobilePay { .. } => amount * 0.01,
}
}
fn payment_fee_demo() {
let methods = vec![
PaymentMethod::Cash,
PaymentMethod::CreditCard {
last4: String::from("1234"),
},
PaymentMethod::MobilePay {
provider: String::from("Alipay"),
},
];
for method in methods {
let fee = calculate_fee(&method, 100.0);
println!("{:?}: fee = ${:.2}", method, fee);
}
}
// === 案例 10:或模式与切片 ===
fn analyze_slice(slice: &[i32]) -> &'static str {
match slice {
[] => "empty",
[_] => "single element",
[first, second] if first == second => "two equal",
[1, _] | [_, 1] => "contains one",
[first @ (2 | 3), ..] => "starts with 2 or 3",
_ => "other pattern",
}
}
fn slice_or_pattern_demo() {
let slices = vec![
vec![],
vec![42],
vec![1, 2],
vec![5, 1],
vec![2, 3, 4],
];
for slice in slices {
println!("{:?}: {}", slice, analyze_slice(&slice));
}
}
// === 案例 11:错误处理中的或模式 ===
#[derive(Debug)]
enum NetworkError {
Timeout,
ConnectionRefused,
DNSFailure,
InvalidResponse,
}
fn is_retryable(error: &NetworkError) -> bool {
matches!(
error,
NetworkError::Timeout
| NetworkError::ConnectionRefused
| NetworkError::DNSFailure
)
}
fn error_handling_demo() {
let errors = vec![
NetworkError::Timeout,
NetworkError::InvalidResponse,
NetworkError::DNSFailure,
];
for error in errors {
println!(
"{:?}: retryable = {}",
error,
is_retryable(&error)
);
}
}
// === 案例 12:或模式与字符串处理 ===
fn classify_token(s: &str) -> &'static str {
match s {
"if" | "else" | "while" | "for" | "loop" => "keyword",
"true" | "false" => "boolean",
"+" | "-" | "*" | "/" => "operator",
s if s.chars().all(|c| c.is_numeric()) => "number",
s if s.chars().all(|c| c.is_alphabetic()) => "identifier",
_ => "unknown",
}
}
fn token_classification_demo() {
let tokens = vec!["if", "true", "+", "123", "myVar", "@"];
for token in tokens {
println!("'{}': {}", token, classify_token(token));
}
}
// === 案例 13:状态机中的或模式 ===
#[derive(Debug, PartialEq)]
enum State {
Idle,
Processing,
Paused,
Completed,
Failed,
}
fn can_transition(from: &State, to: &State) -> bool {
match (from, to) {
(State::Idle, State::Processing) => true,
(State::Processing, State::Paused | State::Completed | State::Failed) => true,
(State::Paused, State::Processing | State::Failed) => true,
(State::Completed | State::Failed, State::Idle) => true,
_ => false,
}
}
fn state_machine_demo() {
let transitions = vec![
(State::Idle, State::Processing),
(State::Processing, State::Paused),
(State::Paused, State::Completed),
];
for (from, to) in transitions {
println!(
"{:?} -> {:?}: {}",
from,
to,
can_transition(&from, &to)
);
}
}
// === 案例 14:或模式提升可读性 ===
fn is_weekend(day: &str) -> bool {
matches!(day, "Saturday" | "Sunday")
}
fn is_business_day(day: &str) -> bool {
matches!(
day,
"Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday"
)
}
fn day_classification_demo() {
for day in vec!["Monday", "Saturday", "Wednesday"] {
println!(
"{}: weekend = {}, business = {}",
day,
is_weekend(day),
is_business_day(day)
);
}
}
// === 案例 15:实际场景------权限检查 ===
#[derive(Debug)]
enum Permission {
Read,
Write,
Execute,
Admin,
}
#[derive(Debug)]
enum Resource {
File,
Directory,
Network,
System,
}
fn check_access(perm: &Permission, res: &Resource) -> bool {
match (perm, res) {
(Permission::Admin, _) => true,
(Permission::Read, Resource::File | Resource::Directory) => true,
(Permission::Write, Resource::File) => true,
(Permission::Execute, Resource::File | Resource::System) => true,
(Permission::Read | Permission::Execute, Resource::Network) => true,
_ => false,
}
}
fn permission_check_demo() {
let cases = vec![
(Permission::Admin, Resource::System),
(Permission::Read, Resource::File),
(Permission::Write, Resource::Network),
];
for (perm, res) in cases {
println!(
"{:?} on {:?}: {}",
perm,
res,
check_access(&perm, &res)
);
}
}
fn main() {
println!("=== Basic Or Pattern ===");
basic_or_pattern_demo();
println!("\n=== Or with Range ===");
or_with_range_demo();
println!("\n=== Nested Or ===");
nested_or_demo();
println!("\n=== Or with @ Binding ===");
or_with_at_binding_demo();
println!("\n=== Tuple Or Pattern ===");
tuple_or_pattern_demo();
println!("\n=== Payment Fee ===");
payment_fee_demo();
println!("\n=== Slice Or Pattern ===");
slice_or_pattern_demo();
println!("\n=== Error Handling ===");
error_handling_demo();
println!("\n=== Token Classification ===");
token_classification_demo();
println!("\n=== State Machine ===");
state_machine_demo();
println!("\n=== Day Classification ===");
day_classification_demo();
println!("\n=== Permission Check ===");
permission_check_demo();
}
或模式与 matches! 宏的协同
matches! 宏是或模式最常见的使用场景之一。它允许我们简洁地检查值是否匹配某个模式,返回布尔值。matches!(value, pattern1 | pattern2 | pattern3) 比手写的 match 表达式更简洁,也比多个 if 条件更清晰。
这种组合在条件检查、过滤、验证等场景中特别有用。例如,values.iter().filter(|&x| matches!(x, 1 | 2 | 3)) 清晰地表达了"筛选出 1、2、3"的意图,比等价的条件表达式 |&x| x == 1 || x == 2 || x == 3 更具声明性。
性能考虑与编译器优化
或模式不会引入运行时开销------编译器会将其展开为高效的机器码。对于连续的字面量,可能生成跳转表;对于稀疏的值,可能生成比较链或二分查找。关键是,使用或模式与手写多个 match 分支的性能完全相同,但代码更简洁。
这再次体现了 Rust 的零成本抽象理念:语法上的便利不会带来性能损失。或模式是纯粹的编译期特性,在 MIR 或 LLVM IR 层面,它与显式分支没有区别。理解这一点能让我们放心使用或模式,不必担心隐藏的性能陷阱。
可读性的提升与代码维护
或模式最大的价值在于提升代码可读性。将逻辑上相关的多个值集中在一个模式中,使代码的结构与业务逻辑更匹配。例如,将所有 HTTP 成功状态码(200、201、204)用或模式组合,比分散在多个分支中更清晰。
从维护角度看,或模式减少了代码重复,降低了修改成本。当需要添加新的匹配值时,只需在或模式中添加一项,而不是复制整个分支。这种局部性使代码更容易理解和修改,降低了引入 bug 的风险。
或模式的局限性
或模式的主要局限在于变量绑定的一致性要求。当不同模式需要绑定不同的变量时,或模式无法使用。这时只能使用多个 match 分支,或者重构代码以统一绑定结构。
另一个限制是,或模式不支持跨越不同类型的匹配。虽然可以写 Some(1) | Some(2),但不能写 Some(x) | Ok(x) 来同时匹配 Option 和 Result。这种类型层面的"或"需要使用枚举或 trait object 等其他抽象。
最佳实践与风格指南
使用或模式时应遵循清晰性原则:只在逻辑上真正相关的值之间使用或模式。如果多个值只是碰巧需要相同的处理逻辑,但在语义上无关,分开写可能更清晰。
格式化方面,当或模式较长时,考虑每个选项占一行并适当对齐。rustfmt 工具对此有良好的支持。保持一致的格式化风格,使或模式在代码库中保持统一的视觉呈现。
结论
或模式是 Rust 模式匹配系统的重要增强,通过 | 操作符提供了简洁的多值匹配能力。理解其语法、变量绑定约束、与其他模式特性的配合,以及编译器的优化特性,是充分利用这一特性的关键。或模式不仅减少了代码重复,更重要的是提升了代码的可读性和可维护性,使逻辑结构更清晰。当你能够识别适合使用或模式的场景,并将其与 @ 绑定、范围模式、守卫等特性灵活组合时,你就真正掌握了现代 Rust 模式匹配的精髓,能够编写既简洁又表达力强的代码。