通过示例学习 Rust 模式匹配

0 阅读6分钟

通过示例学习 Rust 模式匹配

Rust 没有其他语言中常见的 switch 语句,取而代之的是功能更强大、安全性更高的模式匹配(Pattern Matching)。它不仅是语法糖,更是 Rust 核心设计哲学的体现,通过穷尽性检查确保代码安全,同时用简洁的语法表达复杂逻辑。本文将通过实战示例,带你一步步掌握 Rust 模式匹配。

match 表达式:模式匹配的核心

match 是 Rust 模式匹配中最常用的方式,其核心优势在于穷尽性检查(编译器会强制你处理所有可能的情况,避免遗漏)和表达式特性(可直接将匹配结果赋值给变量)。

通用格式与简单示例

match 待匹配的值 {
    模式1 => 执行表达式1,
    模式2 | 模式3 => 执行表达式2, // 多模式合并(逻辑或)
    _ => 兜底表达式, // 通配符,匹配所有剩余情况(必须存在,除非已穷尽所有可能)
}

一个直观的数字范围匹配示例:

fn main() {
    let number = 15;
    // match 是表达式,结果直接赋值给变量
    let range_desc = match number {
        0 => "零",
        1..=9 => "个位数", // 范围匹配(包含端点)
        10..=99 => "两位数",
        _ => "多位数", // 兜底分支,不可或缺
    };
    println!("数字描述:{}", range_desc); // 输出:数字描述:两位数
}

Rust 的模式匹配支持多种模式类型,以下是实际开发中最常用的几种:

枚举模式匹配

这是 Rust 中 match 最常用使用的场景,尤其是针对 Option(空值替代)和 Result(错误处理)两大核心枚举。

匹配 Option 处理空值
fn divide(a: i32, b: i32) -> Option<i32> {
    if b == 0 { None } else { Some(a / b) }
}

fn main() {
    let result = divide(10, 2);
    match result {
        Some(val) => println!("计算结果:{}", val), // 匹配有值的情况
        None => println!("除数不能为0"), // 强制处理空值,避免空指针
    }
}
匹配 Result 处理错误
use std::fs::read_to_string;

fn main() {
    let file_content = read_to_string("test.txt");
    match file_content {
        Ok(content) => println!("文件内容:\n{}", content), // 匹配成功
        Err(e) => println!("读取文件失败:{}", e), // 强制处理错误
    }
}

字面量匹配

匹配固定的字面量值,适合处理固定枚举值、布尔值、固定数字等场景:

fn main() {
    let is_success = true;
    match is_success {
        true => println!("操作成功"),
        false => println!("操作失败"),
    }
}

变量绑定与 @ 绑定

在匹配的同时将值绑定到变量,@ 语法可在匹配范围的同时捕获值:

fn main() {
    let msg = Some(42);
    match msg {
        // 匹配 1-100 范围,同时将值绑定到 n
        Some(n @ 1..=100) => println!("匹配到 1-100 之间的数字:{}", n),
        Some(_) => println!("数字超出范围"),
        None => println!("无值"),
    }
}

结构体与元组解构

直接解构复合类型,提取其中的字段值,无需手动写 getter 方法:

struct Point { x: i32, y: i32 }

fn main() {
    let point = Point { x: 0, y: 5 };
    // 解构结构体匹配
    match point {
        Point { x: 0, y } => println!("点在Y轴上,Y坐标:{}", y),
        Point { x, y: 0 } => println!("点在X轴上,X坐标:{}", x),
        Point { x, y } => println!("点坐标:({}, {})", x, y),
    }

    // 解构元组匹配
    let pair = (2, 0);
    match pair {
        (x, 0) => println!("第二个元素为0,第一个:{}", x),
        (0, y) => println!("第一个元素为0,第二个:{}", y),
        (x, y) => println!("两个元素分别为:{}, {}", x, y),
    }
}

匹配守卫(Match Guard)

匹配守卫可以给模式分支增加额外的条件判断,解决模式本身无法表达的复杂逻辑,语法是在模式后加 if 条件表达式:

fn main() {
    let num = Some(12);
    match num {
        // 先匹配 Some,再判断是否为偶数
        Some(n) if n % 2 == 0 => println!("匹配到偶数:{}", n),
        Some(n) => println!("匹配到奇数:{}", n),
        None => println!("无值"),
    }
}

if let 匹配:单分支匹配的简洁之选

当只需要处理某一种特定情况时,match 会显得冗余。if let 专门用于简化单分支匹配,代码更简洁。

// 用match处理单分支场景,代码冗余
let some_option = Some(42);
match some_option {
    Some(val) => println!("值为:{}", val),
    _ => (), // 其他情况什么都不做,必须写的冗余代码
}

// 用 if let 重构后,代码更加简洁清晰
let some_option = Some(42);
if let Some(val) = some_option {
    println!("值为:{}", val);
}

一些进阶用法如下:

可选的 else 分支

匹配失败时,可以通过 else 分支处理兜底逻辑,替代 match 中的 _ 分支:

fn main() {
    let result: Option<i32> = None;
    if let Some(val) = result {
        println!("有值:{}", val);
    } else {
        println!("无值");
    }
}

支持匹配守卫与条件链

Rust 支持 if let 条件链,可以在匹配的同时增加额外条件,也可以组合多个 if let 匹配,避免嵌套地狱:

fn main() {
    let num = Some(8);
    // 匹配 + 条件判断
    if let Some(n) = num && n > 5 {
        println!("数字大于5:{}", n);
    }

    // 多if let条件链,避免嵌套
    fn get_a() -> Option<i32> { Some(1) }
    fn get_b(a: i32) -> Option<i32> { Some(a + 2) }
    fn get_c(b: i32) -> Option<i32> { Some(b + 3) }

    if let Some(a) = get_a() && let Some(b) = get_b(a) && let Some(c) = get_c(b) {
        println!("最终结果:{}", c);
    }
}

let else:强制匹配成功,否则提前返回

Rust 1.65+ 引入的语法,适合“必须匹配成功,否则报错/返回”的场景:

fn get_count() -> Option<i32> { Some(10) }

fn main() {
    // 匹配成功:count 绑定到当前作用域,全程可用
    let Some(count) = get_count() else {
        panic!("获取计数失败"); // 匹配失败:必须发散(panic/return/break 等)
    };
    println!("计数:{}", count); // 正常使用,无需嵌套

    // 循环中常用:匹配失败直接跳过
    let data = vec![Some(1), None, Some(3), None, Some(5)];
    for item in data {
        let Some(num) = item else { continue };
        println!("处理数字:{}", num);
    }
}

matches! 宏:快速判断是否匹配

如果你只需要判断一个值是否匹配某个模式,而不需要绑定变量或执行复杂逻辑,使用标准库提供的 matches! 宏最方便,它直接返回布尔值。

matches!(待匹配的值, 模式)

判断 Option 是否为 Some

fn main() {
    let some_value = Some(42);
    let none_value: Option<i32> = None;

    println!("some_value 是 Some 吗?{}", matches!(some_value, Some(_))); // true
    println!("none_value 是 Some 吗?{}", matches!(none_value, Some(_))); // false
}

结合迭代器 filter 快速过滤

matches! 非常适合在迭代器适配器中快速过滤元素,无需写冗长的闭包:

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6];
    // 过滤出偶数
    let evens: Vec<i32> = numbers
        .into_iter()
        .filter(|&n| matches!(n, 2 | 4 | 6)) // 匹配指定字面量
        .collect();
    println!("偶数:{:?}", evens); // [2, 4, 6]

    // 过滤出 1-5 之间的数字
    let in_range: Vec<i32> = vec![0, 1, 3, 5, 7]
        .into_iter()
        .filter(|&n| matches!(n, 1..=5)) // 匹配范围
        .collect();
    println!("1-5 之间的数字:{:?}", in_range); // [1, 3, 5]
}

模式匹配中的变量遮蔽(Shadowing)

在模式匹配中,分支内绑定的同名变量会“遮蔽”外部变量,这是 Rust 的常见写法,但需注意作用域:

match 分支中的变量遮蔽

fn main() {
    let value = Some(10); // 外部变量 value
    match value {
        Some(value) => { // 分支内的 value 遮蔽外部
            println!("匹配到的值:{}", value); // 10(使用分支内的 value)
        }
        None => println!("无值"),
    }
    // 离开分支后,外部 value 恢复可用
    println!("外部的 value:{:?}", value); // Some(10)
}

总结

Rust 模式匹配,是替代其他语言 switch 的安全、简洁的分支处理工具,核心优势是编译器会强制检查所有情况,从根源避免漏处理的 bug。

一句话:多分支用 match,单分支用 if let,必成功用 let else,只判断用 matches!。用好模式匹配,就能写出更安全、更好读的 Rust 代码。