在这里插入图片描述


0 开场:为什么需要 Match Guard?

在 Rust 中,模式匹配(match)已经非常强大,但当 模式本身不足以表达条件 时,
匹配守卫(guard) 提供了 “额外布尔表达式” 的钩子:

match value {
    Some(x) if x > 0 => println!("正数"),
    _               => println!("其它"),
}

guard 表达式 在模式匹配成功后绑定变量可用之后 运行,
因此它能:

  • 使用 绑定变量 做额外判断
  • 避免 嵌套 if let
  • 零成本抽象 中保持可读性

本文将:

  1. 拆解 guard 的 求值顺序、生命周期、drop 行为
  2. 4 段递进代码 展示 guard 在 FFI、流解析、实时系统 中的落地
  3. 最终手写一个 零拷贝 JSON 流解析器,把 guard 写进 宏规则

1 语言规则:Match Guard 的三把钥匙

钥匙描述示例
求值时机模式匹配成功后Some(x) if x > 0
绑定可见可使用已绑定的变量Point { x, y } if x == y
零成本编译期折叠,无运行时调度LLVM 常量折叠

guard 失败时,不会 drop 已绑定的变量,继续尝试下一条分支。


2 初级:基础语法与生命周期

2.1 基本用法

#[derive(Debug)]
enum Msg {
    Ping(u64),
    Pong(u64),
    Quit,
}

fn classify(m: Msg) -> &'static str {
    match m {
        Msg::Ping(ts) if ts > 0 => "positive ping",
        Msg::Ping(_)            => "zero ping",
        Msg::Pong(ts) if ts % 2 == 0 => "even pong",
        Msg::Pong(_)            => "odd pong",
        Msg::Quit               => "quit",
    }
}

fn main() {
    println!("{}", classify(Msg::Ping(42)));   // positive ping
    println!("{}", classify(Msg::Pong(7)));    // odd pong
}

2.2 生命周期与 drop

struct Loud(i32);
impl Drop for Loud {
    fn drop(&mut self) {
        println!("drop {}", self.0);
    }
}

fn demo() {
    let x = Loud(1);
    match x {
        Loud(v) if v > 0 => println!(">0"),
        _               => println!("<=0"),
    }
}

输出:

>0
drop 1
  • 只有 匹配成功的分支 的绑定变量 进入作用域
  • 未匹配分支的绑定变量 立即 drop

3 中级:guard 与部分移动

3.1 结构体字段级守卫

#[derive(Debug)]
struct Point { x: i32, y: i32 }

fn quadrant(p: Point) -> &'static str {
    match p {
        Point { x, y } if x > 0 && y > 0 => "I",
        Point { x, y } if x < 0 && y > 0 => "II",
        Point { x, y } if x < 0 && y < 0 => "III",
        Point { x, y } if x > 0 && y < 0 => "IV",
        _ => "axis",
    }
}
  • guard 使用 已绑定的字段
  • 部分移动 不受 guard 影响

4 高级:流解析器中的 guard

需求:

  • 零拷贝解析 JSON
  • 用 guard 处理 数字、字符串、嵌套对象

4.1 数据结构

#[derive(Debug)]
enum Token<'a> {
    Null,
    Bool(bool),
    Num(&'a str),
    Str(&'a str),
    Arr(Vec<Token<'a>>),
    Obj(Vec<(&'a str, Token<'a>)>),
}

4.2 解析器骨架

struct Parser<'a> {
    src: &'a str,
    pos: usize,
}

impl<'a> Parser<'a> {
    fn parse(&mut self) -> Token<'a> {
        self.skip_ws();
        match self.peek() {
            Some(b'n') => { self.consume("null"); Token::Null }
            Some(b't') => { self.consume("true"); Token::Bool(true) }
            Some(b'f') => { self.consume("false"); Token::Bool(false) }
            Some(b'"') => Token::Str(self.parse_str()),
            Some(b'[') => self.parse_arr(),
            Some(b'{') => self.parse_obj(),
            _ => Token::Num(self.parse_num()),
        }
    }

    fn parse_str(&mut self) -> &'a str {
        let start = self.pos + 1;
        self.bump(); // skip '"'
        while self.peek() != Some(b'"') { self.bump(); }
        let end = self.pos;
        self.bump(); // skip closing '"'
        &self.src[start..end]
    }

    fn parse_num(&mut self) -> &'a str {
        let start = self.pos;
        while self.peek().map_or(false, |c| c.is_ascii_digit()) { self.bump(); }
        &self.src[start..self.pos]
    }

    fn parse_arr(&mut self) -> Token<'a> {
        self.bump(); // '['
        self.skip_ws();
        let mut out = Vec::new();
        while self.peek() != Some(b']') {
            out.push(self.parse());
            self.skip_ws();
            if self.peek() == Some(b',') { self.bump(); self.skip_ws(); }
        }
        self.bump(); // ']'
        Token::Arr(out)
    }

    fn parse_obj(&mut self) -> Token<'a> {
        self.bump(); // '{'
        self.skip_ws();
        let mut out = Vec::new();
        while self.peek() != Some(b'}') {
            let key = self.parse_str();
            self.skip_ws();
            assert_eq!(self.bump(), Some(b':'));
            self.skip_ws();
            let val = self.parse();
            out.push((key, val));
            self.skip_ws();
            if self.peek() == Some(b',') { self.bump(); self.skip_ws(); }
        }
        self.bump(); // '}'
        Token::Obj(out)
    }

    fn peek(&self) -> Option<u8> {
        self.src.as_bytes().get(self.pos).copied()
    }
    fn bump(&mut self) -> Option<u8> {
        let c = self.peek();
        self.pos += 1;
        c
    }
    fn skip_ws(&mut self) {
        while self.peek().map_or(false, u8::is_ascii_whitespace) { self.bump(); }
    }
    fn consume(&mut self, s: &str) {
        for &b in s.as_bytes() {
            assert_eq!(self.bump(), Some(b));
        }
    }
}

4.3 使用示例

fn main() {
    let json = r#"{"name":"rust","version":1.70,"tags":["safe","fast"]}"#;
    let mut p = Parser { src: json, pos: 0 };
    let tok = p.parse();
    println!("{:#?}", tok);
}

5 实时系统:guard 与零成本抽象

5.1 需求

  • 实时音频回调 禁止 malloc
  • 使用 栈数组 + guard 保证异常安全

5.2 代码

struct Guard<'a> {
    ptr: &'a mut [f32],
}

impl<'a> Guard<'a> {
    fn new(buf: &'a mut [f32]) -> Self {
        Self { ptr: buf }
    }
}

impl Drop for Guard<'_> {
    fn drop(&mut self) {
        for x in self.ptr.iter_mut() {
            *x = 0.0; // 清零
        }
    }
}

fn process(buf: &mut [f32]) {
    let _g = Guard::new(buf);
    // 实时处理...
}

6 宏级 DSL:把 guard 写进宏

macro_rules! match_guard {
    ($expr:expr, $($pat:pat if $cond:expr => $body:expr),+ $(,)?) => {
        match $expr {
            $($pat if $cond => $body),+
        }
    };
}

fn classify(n: i32) -> &'static str {
    match_guard!(n,
        i if i > 0 => "positive",
        i if i < 0 => "negative",
        _          => "zero",
    )
}

7 性能与零成本

场景代价原因
基本 guard0LLVM 常量折叠
复杂 guard0分支合并
嵌套 if let>0额外分支

8 常见误区 & 工具链

误区修复工具
guard 中使用未绑定变量使用已绑定变量编译器报错
guard 失败导致变量未 drop编译器保证Miri
大 guard 表达式提炼函数clippy::complexity

9 结语:guard 不是语法糖,而是“模式 + 布尔”的零成本组合

  • 绑定后判断:避免嵌套 if let
  • 零成本:编译期折叠
  • 异常安全:未匹配分支立即 drop

掌握 guard,你就拿到了 可读性 + 性能 + 安全 的三重增益。
在这里插入图片描述

更多推荐