Skip to content

模式匹配

match 和 if let

match 跟 switch 非常像,_ 类似于 switch 中的 default。

  • match 的匹配必须要穷举出所有可能,因此这里用 _ 来代表未列出的所有可能性
  • match 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同
  • X | Y,类似逻辑运算符 或,代表该分支可以匹配 X 也可以匹配 Y,只要满足一个即可
rust
match target {
    模式1 => 表达式1,
    模式2 => {
        语句1;
        语句2;
        表达式2
    },
    _ => 表达式3
}



// match 本身也是一个表达式,因此可以用它来赋值
enum IpAddr {
   Ipv4,
   Ipv6
}

fn main() {
    let ip1 = IpAddr::Ipv6;
    let ip_str = match ip1 {
        IpAddr::Ipv4 => "127.0.0.1",
        _ => "::1",
    };

    println!("{}", ip_str);
}






let some_u8_value = 0u8;
match some_u8_value {
    1 => println!("one"),
    3 => println!("three"),
    5 => println!("five"),
    7 => println!("seven"),
    _ => (),
}
// 通过将 _ 其放置于其他分支后,_ 将会匹配所有遗漏的值。() 表示返回单元类型与所有分支返回值的类型相同,所以当匹配到 _ 后,什么也不会发生。




if let Some(3) = v {
    println!("three");
}

解构 Option

因为 OptionSomeNone 都包含在 prelude 中,因此你可以直接通过名称来使用它们,而无需以 Option::Some 这种形式去使用,总之,千万不要因为调用路径变短了,就忘记 Some 和 None 也是 Option 底下的枚举成员!

匹配 Option<T>

rust
fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);

模式适用场景

模式是 Rust 中的特殊语法,它用来匹配类型中的结构和数据,它往往和 match 表达式联用,以实现强大的模式匹配能力。模式一般由以下内容组合而成:

  • 字面值
  • 解构的数组、枚举、结构体或者元组
  • 变量
  • 通配符
  • 占位符
rust
match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}





match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    _ => EXPRESSION,
}



if let PATTERN = SOME_VALUE {

}




// Vec是动态数组
let mut stack = Vec::new();

// 向数组尾部插入元素
stack.push(1);
stack.push(2);
stack.push(3);

// stack.pop从数组尾部弹出元素
while let Some(top) = stack.pop() {
    println!("{}", top);
}



let PATTERN = EXPRESSION;
// let也是模式匹配



let (x, y) = (1, 2, 3);
// 报错
// 对于元组来说,元素个数也是类型的一部分!


// 函数参数也是模式:
fn foo(x: i32) {
    // 代码
}


let Some(x) = some_option_value;
// 报错
// 因为右边的值可能不为 Some,而是 None,这种时候就不能进行匹配,也就是上面的代码遗漏了 None 的匹配。

if let Some(x) = some_option_value {
    println!("{}", x);
}
// 因为 if let 允许匹配一种模式,而忽略其余的模式( 可驳模式匹配 )。

全模式列表

匹配字面值

rust

let x = 1;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("anything"),
}

匹配命名变量

rust

fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(y) => println!("Matched, y = {:?}", y),
        _ => println!("Default case, x = {:?}", x),
    }

    println!("at the end: x = {:?}, y = {:?}", x, y);
}

单分支多模式

rust

let x = 1;

match x {
    1 | 2 => println!("one or two"),
    3 => println!("three"),
    _ => println!("anything"),
}

通过序列 ..= 匹配值的范围

rust
let x = 5;

match x {
    1..=5 => println!("one through five"),
    _ => println!("something else"),
}


let x = 'c';

match x {
    'a'..='j' => println!("early ASCII letter"),
    'k'..='z' => println!("late ASCII letter"),
    _ => println!("something else"),
}

解构并分解值

可以使用模式来解构结构体、枚举、元组、数组和引用。

下面代码展示了如何用 let 解构一个带有两个字段 x 和 y 的结构体 Point:

rust
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x: a, y: b } = p;
    // 新建了两个变量a,b

    //    也可以简写成
    let Point { x, y } = p;

    assert_eq!(0, a);
    assert_eq!(7, b);
}

下文展示了固定某个字段的匹配方式:

rust

fn main() {
    let p = Point { x: 0, y: 7 };

    match p {
        Point { x, y: 0 } => println!("On the x axis at {}", x),
        Point { x: 0, y } => println!("On the y axis at {}", y),
        Point { x, y } => println!("On neither axis: ({}, {})", x, y),
    }
}

首先是 match 第一个分支,指定匹配 y 为 0 的 Point; 然后第二个分支在第一个分支之后,匹配 y 不为 0,x 为 0 的 Point; 最后一个分支匹配 x 不为 0,y 也不为 0 的 Point。

match 这样用就很奈斯了;

下面代码以 Message 枚举为例,编写一个 match 使用模式解构每一个内部值:

rust
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.")
        }
        Message::Move { x, y } => {
            // 模式匹配一样要类型相同,因此匹配 Message::Move{1,2} 这样的枚举值,就必须要用 Message::Move{x,y} 这样的同类型模式才行。
            println!(
                "Move in the x direction {} and in the y direction {}",
                x,
                y
            );
        }
        Message::Write(text) => println!("Text message: {}", text),
        Message::ChangeColor(r, g, b) => {
            println!(
                "Change the color to red {}, green {}, and blue {}",
                r,
                g,
                b
            )
        }
    }
}

match 也可以匹配嵌套的项!

rust
enum Color {
   Rgb(i32, i32, i32),
   Hsv(i32, i32, i32),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(Color),
}

fn main() {
    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));

    match msg {
        Message::ChangeColor(Color::Rgb(r, g, b)) => {
            println!(
                "Change the color to red {}, green {}, and blue {}",
                r,
                g,
                b
            )
        }
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println!(
                "Change the color to hue {}, saturation {}, and value {}",
                h,
                s,
                v
            )
        }
        _ => ()
    }
}

解构结构体和元组

rust
struct Point {
     x: i32,
     y: i32,
 }

let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });

println!("feet={:?}, inches = {:?},x= {:?},y= {:?}", feet, inches,x,y);

//  feet=3, inches = 10,x= 3,y= -10

解构数组

定长数组

rust
let arr: [u16; 2] = [114, 514];
let [x, y] = arr;

assert_eq!(x, 114);
assert_eq!(y, 514);

不定长数组

rust

let arr: &[u16] = &[114, 514];

if let [x, ..] = arr {
    assert_eq!(x, &114);
}

if let &[.., y] = arr {
    assert_eq!(y, 514);
}

let arr: &[u16] = &[];

assert!(matches!(arr, [..]));
assert!(!matches!(arr, [x, ..]));

忽略模式中的值

rust
let mut setting_value = Some(5);
let new_setting_value = Some(10);

match (setting_value, new_setting_value) {
    (Some(_), Some(_)) => {
        println!("Can't overwrite an existing customized value");
    }
    _ => {
        setting_value = new_setting_value;
    }
}

println!("setting is {:?}", setting_value);

除了match中,_也可以作为函数的入参

rust
  
fn foo(_: i32, y: i32) {
    println!("This code only uses the y parameter: {}", y);
}

fn main() {
    foo(3, 4);
}

匹配守卫

匹配守卫(match guard)是一个位于 match 分支模式之后的额外 if 条件,它能为分支模式提供更进一步的匹配条件。

rust
let num = Some(4);

match num {
    Some(x) if x < 5 => println!("less than five: {}", x),
    Some(x) => println!("{}", x),
    None => (),
}

match 表达式的模式中新建了一个变量而不是使用 match 之外的同名变量。内部变量覆盖了外部变量,意味着此时不能够使用外部变量的值,下面代码展示了如何使用匹配守卫修复这个问题。

rust
fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(n) if n == y => println!("Matched, n = {}", n),
        _ => println!("Default case, x = {:?}", x),
    }

    println!("at the end: x = {:?}, y = {}", x, y);
}
rust



let x = 4;
let y = false;

match x {
    4 | 5 | 6 if y => println!("yes"),
    _ => println!("no"),
}


// no

匹配优先级为:(4 | 5 | 6) if y => ...

@绑定

在 Rust 中,@ 符号用于模式匹配中的宏调用。它是一个特殊的语法,用于将宏的输出结果绑定到一个变量上。

通常,宏调用的结果会被展开为一些代码片段,并插入到调用位置。但是,有时我们希望将宏的输出结果作为一个整体进行处理,例如在模式匹配中使用。这时就可以使用 @ 符号来将宏的输出结果绑定到一个变量上,并在模式匹配中使用该变量。

rust
enum Message {
    Hello { id: i32 },
}

let msg = Message::Hello { id: 5 };

match msg {
    // 判断Message::Hello 的 id 字段是否位于 3..=7 内
    Message::Hello { id: id_variable @ 3..=7 } => {
        // 等于
        // Message::Hello { id: id_variable } if (id_variable >= 3 && id_variable <= 7) => {

        println!("Found an id in range: {}", id_variable)
    },
    Message::Hello { id: 10..=12 } => {
        println!("Found an id in another range")
    },
    Message::Hello { id } => {
        println!("Found some other id: {}", id)
    },
}

如果上面的看不懂,那看下面的对比:

修改前:

rust
fn main() {
    let num = Some(4);
    match num {
        Some(x) if x < 5 => println!("less than five: {}", x),
        Some(x) => println!("{}", x),
        None => (),
    }
}

修改后:

rust
fn main() {
    let num = Some(4);
    match num {
        Some(x @ 0..5 )=> println!("less than five: {}", x),
        Some(x) => println!("{}", x),
        None => (),
    }
}