rust笔记-模式匹配

模式匹配

模式匹配允许程序在处理不同类型的数据时使用不同的代码。在某些编程语言中,模式匹配被实现为一种特殊的关键字或语法结构,在Rust中,可以使用match关键字和if let进行模式匹配。

在Rust中,可以使用match关键字进行模式匹配。以下是一个简单的示例:

fn main() {
   let x = 1;
   match x {
       1 => println!("x is 1"),
       _ => println!("x is not 1"),
   }
}

在这个例子中,我们使用match关键字来匹配变量x的值。如果x的值为1,则执行第一个分支,即打印x is 1。否则,执行第二个分支,即打印x is not 1。

模式匹配在处理不同类型的数据时非常有用,因为它允许程序根据数据的不同特性使用不同的代码。这使得代码更加灵活和可读性更高。

这里我们想去匹配 dire 对应的枚举类型,因此在 match 中用三个匹配分支来完全覆盖枚举变量 Direction 的所有成员类型,有以下几点值得注意:

match 的匹配必须要穷举出所有可能,因此这里用 _ 来代表未列出的所有可能性
match 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同
X | Y,类似逻辑运算符 或,代表该分支可以匹配 X 也可以匹配 Y,只要满足一个即可
其实 match 跟其他C语言中的 switch 非常像,_ 类似于 switch 中的 default。

match匹配

match 表达式允许我们根据一个表达式的值来选择一个分支。

match 表达式的一般形式如下:

match variable {
    pattern1 => expression1,
    pattern2 => expression2,
    pattern3 => expression3,
    ...
    patternN => expressionN,
    _ => expression,
}

在 match 表达式中,variable 是一个表达式,pattern1 到 patternN 都是模式,expression1 到 expressionN

下面我们通过一个例子在看一下match 的使用:

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny =>  {
            println!("Lucky penny!");
            1
        },
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

value_in_cents 函数根据匹配到的硬币,返回对应的美分数值。match 后紧跟着的是一个表达式,跟 if 很像,但是 if 后的表达式必须是一个布尔值,而 match 后的表达式返回值可以是任意类型,只要能跟后面的分支中的模式匹配起来即可,这里的 coin 是枚举 Coin 类型。

接下来是 match 的分支。一个分支有两个部分:一个模式和针对该模式的处理代码。第一个分支的模式是 Coin::Penny,其后的 => 运算符将模式和将要运行的代码分开。这里的代码就仅仅是表达式 1,不同分支之间使用逗号分隔。

当 match 表达式执行时,它将目标值 coin 按顺序依次与每一个分支的模式相比较,如果模式匹配了这个值,那么模式之后的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支。

每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 match 表达式的返回值。如果分支有多行代码,那么需要用 {} 包裹,同时最后一行代码需要是一个表达式。

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);
}

模式绑定

match 表达式中的模式可以是一个变量,这样就可以将一个值绑定到一个模式上,并在 match 代码块中使用该变量。

模式匹配的另外一个重要功能是从模式中取出绑定的值,例如:

#[derive(Debug)]
enum UsState {
    Alabama,
    Alaska,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState), // 25美分硬币
}

其中 Coin::Quarter 成员还存放了一个值:美国的某个州(因为在 1999 年到 2008 年间,美国在 25 美分(Quarter)硬币的背后为 50 个州印刷了不同的标记,其它硬币都没有这样的设计)。

接下来,我们希望在模式匹配中,获取到 25 美分硬币上刻印的州的名称:

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("State quarter from {:?}!", state);
            25
        },
    }
}

上面代码中,在匹配 Coin::Quarter(state) 模式时,我们把它内部存储的值绑定到了 state 变量上,因此 state 变量就是对应的 UsState 枚举类型。

例如有一个印了阿拉斯加州标记的 25 分硬币:Coin::Quarter(UsState::Alaska), 它在匹配时,state 变量将被绑定 UsState::Alaska 的枚举值。

再来看一个更复杂的例子:

enum Action {
    Say(String),
    MoveTo(i32, i32),
    ChangeColorRGB(u16, u16, u16),
}

fn main() {
    let actions = [
        Action::Say("Hello Rust".to_string()),
        Action::MoveTo(1,2),
        Action::ChangeColorRGB(255,255,0),
    ];
    for action in actions {
        match action {
            Action::Say(s) => {
                println!("{}", s);
            },
            Action::MoveTo(x, y) => {
                println!("point from (0, 0) move to ({}, {})", x, y);
            },
            Action::ChangeColorRGB(r, g, _) => {
                println!("change color into '(r:{}, g:{}, b:0)', 'b' has been ignored",
                    r, g,
                );
            }
        }
    }
}

运行后输出:

$ cargo run
   Compiling world_hello v0.1.0 (/Users/sunfei/development/rust/world_hello)
    Finished dev [unoptimized + debuginfo] target(s) in 0.16s
     Running `target/debug/world_hello`
Hello Rust
point from (0, 0) move to (1, 2)
change color into '(r:255, g:255, b:0)', 'b' has been ignored

穷尽匹配

match 语句可以穷尽所有的可能情况,如果匹配失败,编译器会报错。

enum Action {
    Up,
    Down,
    Left,
    Right,
}

fn main() {
    let dir = Action::Up;
    match dir {
        Action::Up => println!("Going up"),
        Action::Left => println!("Going left"),
        Action::Right => println!("Going right"),
        Action::Down => println!("Going down"),
    };
}

上面例子中我们定义了四个枚举值,但是当只匹配了其中的三个,编译器会报错,这样在编译阶段你就可以知道哪些分支没有覆盖,从而避免一些错误。

在本节第一个例子中,我们使用了 _ 通配符,表示忽略当前分支的值,这样编译器就不会报错了。通配符放置于其他分支后,匹配所有遗漏的值。除了通配符还可以使用一个变量承载其他情况。下面我们看一个使用通配符和用变量承载其他情况的例子。

#[derive(Debug)]
enum Action {
    Up,
    Down,
    Left,
    Right,
}

fn main() {
    let dir = Action::Up;
     match dir {
         Action::Up => println!("Going up"),
         Action::Left => println!("Going left"),
        _ => (),
    };

    match dir {
        Action::Left => println!("Going left"),
        action=> println!("I don't know how to go {:?}", action),
    };
}

if let匹配

如果你只需要匹配一个值,可以使用 if let 语法。

#[derive(Debug)]
enum Action {
    Up,
    Down,
    Left,
    Right,
}

fn main() {
    let dir = Action::Up;
    if let Action::Up = dir {
        println!("Going up");
    }
}

matches!宏

matches! 宏可以用来检查一个值是否符合一个条件,即可以将一个表达式跟模式进行匹配,然后返回匹配结果(true 或者 false)。

#[derive(Debug)]
enum Action {
    Up,
    Down,
    Left,
    Right,
}

fn main() {
    let dir = Action::Up;
    if matches!(dir, Action::Up) {
        println!("Going up");
    }
}

变量遮蔽

当一个模式匹配成功后,匹配到的值会被保存为变量,这个变量会遮蔽掉外层作用域中的同名变量。match 表达式中的变量遮蔽不容易被发现,所以建议不要使用同名变量。

#[derive(Debug)]
enum Action {
    Up,
    Down,
    Left,
    Right,
}

fn main() {
    let dir = Action::Up;
    if matches!(dir, Action::Up) {
        println!("Going up");
    }
}

运行结果如下:

在匹配前,age是Some(30)
匹配出来的age是30
在匹配后,age是Some(30)

在 if let 中,= 右边 Some(i32) 类型的 age 被左边 i32 类型的新 age 遮蔽了,该遮蔽一直持续到 if let 语句块的结束。因此第三个 println! 输出的 age 依然是 Some(i32) 类型。

上面代码可以改为:

fn main() {
   let age = Some(30);
   println!("在匹配前,age是{:?}", age);
   match age {
       Some(x) =>  println!("匹配出来的age是{}", x),
       _ => ()
   }
   println!("在匹配后,age是{:?}", age);
}
posted @   NoodlesYang  阅读(14)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示