Rust编程思想(六)- 赶走switch语句的match表达式

内容分享3周前发布
0 0 0

在C语言系里,包括C++、C#、Java、JavaScript等超级流行的语言,以及Go、Swift等新兴语言也在用switch-case语句进行条件分支匹配,而Rust使用了一种新的条件分支匹配的模式match,类似于Kotlin中的when,但Rust中的match更加强劲。开始有些时候感觉还是switch比较方便,但用习惯了会觉得match真的很爽,太方便。

定义

在Rust中,match表达式是一种用于模式匹配和控制流的结构。它允许您根据给定值的不同模式执行不同的代码块,提供了超级灵活和强劲的模式匹配功能。

基本语法:

match value {
    pattern1 => { /* block */ },
    pattern2 => { /* block */ },
    pattern3 if condition => { /* block */ },
    _ => { /* default block */ }
}

其中:

  1. value 是需要进行模式匹配的值。
  2. pattern1、pattern2 等是不同的模式,用于匹配 value 的可能取值。
  3. condition 是可选的条件,可以在模式匹配的基础上进一步筛选匹配的情况。
  4. _ 是通配符,用于匹配所有其他情况。
  5. 大括号 {} 内的代码块包含了在模式匹配成功时需要执行的代码。

例如:

fn common_match() {
    let num = 1;
    match num {
        1 => println!("this num is 壹"),
        2 => println!("this num is 贰"),
        3..=10 => println!("this num is {}", num),
        _ => println!("this num is {}", num)
    }
}

不过个人不太喜爱这里的_作为默认分支,简洁倒是够简洁,这可能也是Rust的核心设计思想之一,尽量让程序员少敲几个字符,但可读性稍微就差了一些,尤其是对于历史项目和新学习Rust的程序员不太友善。希望后面还是能把default关键词给支持一下,或者来个others,这样更接近自然语言的表达。

匹配臂

匹配臂(match arm) = 模式(Pattern) + 代码块(Block)

  1. 模式:用于匹配待匹配值的结构或者特征。
  2. 代码块:匹配成功后执行的代码块。

模式

模式是一种用于匹配和解构数据的语法格式。不止用于match表达式,还运用在if let表达式、for循环的解构中。其作用在于简单、清晰地处理不同的数据结构和情况。

常见的匹配模式有:

  • 常量模式(字面量模式):直接匹配常量值。
  • 范围模式:匹配范围,如 0..=10。
  • 通配符模式:使用 _ 匹配任意值。

常量模式

fn constant_match() {
    let number = 100;
    match number {
        1 => println!("number is 1"),// 常量匹配
        2..=10 => println!("number is {}", number),//范围匹配
        _ => println!("number is {}", number)//通配符匹配
    }
}

枚举模式
匹配枚举的变体。

fn enum_match() {
    enum Fruit {
        Apple,
        Orange,
        Banana
    }

    let favorite_fruit = Fruit::Apple;
    match favorite_fruit {
        Fruit::Apple => println!("我喜爱吃苹果"),
        Fruit::Orange => println!("我喜爱吃橙子"),
        Fruit::Banana => println!("我喜爱吃香蕉")
    }
}

Rust的核心理念是保障软件的可靠性,所以match对于enum类型的检查也更严格,匹配臂必须包含全部的enum值,或者通过_通配符来匹配没有匹配到的情况(不提议)。而C/C++/Java等都没有严格要求,但IDE有时可以通过lint插件提升代码质量。而Rust把模式的严格性写进了语言中,骨子里对可靠性进行严格要求,尽最大的可能性让程序员不要忘记处理业务逻辑。

变量模式
使用变量名匹配,并将值绑定到变量上。这里它实则有一个专有名词,叫模式绑定,即可以从模式中取出绑定的值

fn variable_match() {
    let num = Some(600);
    match num {
        Some(v) => println!("num is {}", v),
        None => println!("未匹配到值")
    }
}

这个特性实则是和Result/Option的结合使用的,在匹配Option/Result时,如果匹配成功,会返回Option/Result中的值,如果匹配失败,会返回None/Err。如果Result/Option中有返回值可以把值取出进行后续的处理。这样就不会感觉到使用unwrap来取值那么麻烦。if-let用于Result更简洁一些,对于Option还是match方便,而且便于日后扩展。

注意,match的模式匹配也会交出所有权的,这里的num匹配守了num就没有值了,不能再使用了。

引用模式
对于只读的模式处理,使用 & 匹配引用,这样match不会带走所有权。

fn reference_match() {
    let value = 42;
    match &value {
        &x => println!("Got a reference to {}", x),
    }

    match &value {
        ref y => println!("Got a reference to {}", y),
    }
}

这里ref进一步声明y是引用,只是声明一个变量,而不是定义一个变量,也不会遵守借用规则带走所有权。

结构体模式
按结构体的字段来匹配,所有字段都必须匹配才成功。

fn struct_match() {
    struct Coordinate {
        lon: f64,
        lat: f64,
    }

    let coord = Coordinate { lon: 116.420941, lat: 39.922018 };
    match coord {
        Coordinate { lon, lat } => println!("lon: {}, lat: {}", lon, lat)
    }
}

元组模式
匹配元组的元素,所有元素都匹配才成功。

fn tuple_match() {
    let tuple = (1, 2, 3);
    match tuple {
        (x, y, z) => println!("x: {}, y: {}, z: {}", x, y, z),
    }
}

判断模式
使用 if 关键字添加额外条件,如 pattern if condition。
概念上,我们把这种情况叫做匹配守卫。模式守卫出目前模式的后面,由关键字 if 后面的布尔类型表达式组成。当模式匹配成功时,将执行匹配守卫表达式。如果此表达式的计算结果为真,则此模式将进一步被确认为匹配成功。 否则,匹配将测试下一个模式,包括测试同一匹配臂中运算符 | 分割的后续匹配模式。

fn condition_match() {
    let month = 2;
    let year = 2024;

    let days = match month {
        2 if year % 4 == 0 => 29,
        2 => 28,
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        4 | 6 | 9 | 11 => 30,
        _ => 0
    };

    println!("this month has {} days", days)
}

这个特性超级好用,在C语言中常常不加break,让多个case语句运行到第一个匹配的case,然后继续运行。这样在后续维护中超级容易出错,而Rust则要求所有case必须全部列出,对于多个匹配臂使用一个处理模式时可以使用|并列在一起,超级贴心。

注意事项

  1. 注意匹配臂的排列顺序。
    match 表达式的执行流程是从上至下逐个尝试每个模式
    ,直到找到匹配的模式为止。一旦找到匹配的模式,将执行对应的代码块,并且整个 match 表达式的值就是执行代码块的结果值。
fn match_sequence() {
    let y = Some(3);

    match y {
        Some(v) => println!("去泰山"),
        Some(3) => println!("去黄山"),
        None => println!("在家待着")
    }
}
warning: unreachable pattern
   --> src/main.rs:120:9
    |
120 |         Some(3) => println!("去黄山"),
    |         ^^^^^^^
    |
    = note: `#[warn(unreachable_patterns)]` on by default
  1. 单个匹配臂中可以匹配多个条件,可以使用操作符 | 连接多个匹配模式。 每个模式将按照从左到右的顺序进行测试,直到找到一个成功的匹配。
  2. 穷尽匹配。match表达式的匹配必须穷尽所有情况。
  3. 通配符。通过将 _ 其放置于其他分支后, 将会匹配所有遗漏的值。()
    表明返回单元类型与所有分支返回值的类型一样,所以当匹配到 _ 后,什么也不会发生。除了_通配符,用一个变量来承载其他情况也是可以的。
fn multiple_match() {
    let month = 10;
    match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => println!("有31天"),
        4 | 6 | 9 | 11 => println!("有30天"),
        2 => println!("不必定是28天还是29天,看年份"),
        // _ => ()
        // _other => ()
        // other_number => println!("没有{}这个月份", other_number)
        // other_number => ()
    }
}
error[E0004]: non-exhaustive patterns: `i32::MIN..=0_i32` and `13_i32..=i32::MAX` not covered
   --> src/main.rs:128:11
    |
128 |     match month {
    |           ^^^^^ patterns `i32::MIN..=0_i32` and `13_i32..=i32::MAX` not covered
    |
    = note: the matched value is of type `i32`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern, a match arm with multiple or-patterns as shown, or multiple match arms
    |
131 ~         2 => println!("不必定是28天还是29天,看年份"),
132 ~         i32::MIN..=0_i32 | 13_i32..=i32::MAX => todo!(),
    |
  1. 每个匹配臂要返回一样的类型。由于match是一种表达式,它是可以用来赋值的,叫做匹配赋值
    。如果每个匹配臂的返回类型不能保持一致,那么整个表达式所返回的类型就不能确定,这是有悖于rust的设计原则的,编译无法通过。
fn result_type() {
    let value = 10;
    let result = match value {
        0 => "zero",  // 字符串类型
        1 => 1,       // 整数类型
        _ => false,   // 布尔类型
    };
    println!("Result: {:?}", result);
}
error[E0308]: `match` arms have incompatible types
   --> src/main.rs:142:14
    |
140 |       let result = match value {
    |  __________________-
141 | |         0 => "zero",  // 字符串类型
    | |              ------ this is found to be of type `&str`
142 | |         1 => 1,       // 整数类型
    | |              ^ expected `&str`, found integer
143 | |         _ => false,   // 布尔类型
144 | |     };
    | |_____- `match` arms have incompatible types
© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...