通过示例学习 Rust 模式匹配

通过示例学习 Rust 模式匹配

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

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

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

通用格式与简单示例

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

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

rust 复制代码
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 处理空值
rust 复制代码
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 处理错误
rust 复制代码
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), // 强制处理错误
    }
}

字面量匹配

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

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

变量绑定与 @ 绑定

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

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

结构体与元组解构

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

rust 复制代码
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 条件表达式:

rust 复制代码
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 专门用于简化单分支匹配,代码更简洁。

rust 复制代码
// 用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 中的 _ 分支:

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

支持匹配守卫与条件链

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

rust 复制代码
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+ 引入的语法,适合"必须匹配成功,否则报错/返回"的场景:

rust 复制代码
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! 宏最方便,它直接返回布尔值。

plaintext 复制代码
matches!(待匹配的值, 模式)

判断 Option 是否为 Some

rust 复制代码
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! 非常适合在迭代器适配器中快速过滤元素,无需写冗长的闭包:

rust 复制代码
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 分支中的变量遮蔽

rust 复制代码
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 代码。

相关推荐
Tony Bai1 天前
Rust 看了流泪,AI 看了沉默:扒开 Go 泛型最让你抓狂的“残疾”类型推断
开发语言·人工智能·后端·golang·rust
jump_jump1 天前
RTK:给 AI 编码助手瘦身的 Rust 代理
性能优化·rust·claude
小杍随笔1 天前
【Rust Exercism 练习详解:Anagram + Space Age + Sublist(附完整代码与深度解读)】
开发语言·rust·c#
Rust研习社1 天前
Rust 字符串与切片实战
rust
朝阳5811 天前
局域网聊天工具
javascript·rust
朝阳5811 天前
我做了一个局域网传文件的小工具,记录一下
javascript·rust
Rust语言中文社区2 天前
【Rust日报】用 Rust 重写的 Turso 是一个更好的 SQLite 吗?
开发语言·数据库·后端·rust·sqlite
小杍随笔2 天前
【Rust 半小时速成(2024 Edition 更新版)】
开发语言·后端·rust
Source.Liu2 天前
【office2pdf】office2pdf 纯 Rust 实现的 Office 转 PDF 库
rust·pdf·office2pdf
洛依尘2 天前
深入浅出 Rust 生命周期:它不是语法负担,而是借用关系的说明书
后端·rust