当前位置: 首页 > article >正文

【Rust自学】18.3. 模式(匹配)的语法

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

18.3.1. 匹配字面值

模式可以直接匹配字面值。看个例子:

let x = 1;

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

此代码打印one ,因为x中的值为 1。当希望代码在获取特定具体值时执行操作时,此语法非常有用。

18.3.2. 匹配命名变量

命名的变量是可匹配任何值的无可辩驳模式。看个例子:

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 = {x:?}, y = {y}");

这个例子的逻辑很简单,主要是看这段代码中出现两个y。这两个y没有任何关系,处于不同的作用域,let y = 10y就是为了存储10,而Some(y)y主要是提取Option类型下的Some变体里附带的数据。

match里的执行逻辑:

  • 第一个分支中的模式与x的定义值不匹配,因此代码继续。

  • 第二个匹配臂中的模式引入了一个名为y的新变量,它将匹配Some值内的任何值。因为我们处于match表达式内的新作用域,所以这是一个新的y变量,而不是我们在开头声明的值为10的y 。这个新y绑定将匹配Some内的任何值,这就是我们所拥有的在x中。因此,这个新的y绑定到xSome的内部值。该值为5 ,因此该臂的表达式将执行并打印Matched, y = 5

  • 如果xNone值而不是Some(5) (当然这个例子里不可能),则前两个臂中的模式将不匹配,因此该值将与下划线匹配。我们没有在下划线臂的模式中引入x变量,因此表达式中的x仍然是没有被遮蔽的外部x 。在这个假设的情况下, match将打印Default case, x = None

输出:

Matched, y = 5
at the end: x = Some(5), y = 10

18.3.3. 多重模式

match表达式里,使用管道符|语法(就是的意思),可以匹配多种模式。看个例子:

let x = 1;

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

例子中的第一个分支就是x为1或2都能匹配。

18.3.4. 使用..=来匹配某个范围的值

看例子:

let x = 5;

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

这个例子的第一个分支表示当x值为1到5(闭区间),也就是1、2、3、4、5的任意一个时都会匹配。

由于 Rust 可以判断范围是否为空的唯一类型是char和数值,因此范围仅允许包含数字或char值。看个例子:

let x = 'c';

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

这个例子的第一个分支代表匹配从aj的字符,第二个分支代表匹配从kz到字符。

18.3.5. 解构以分解值

我们可以使用模式来解构structenumtuple,从而引用这些类型值的不同部分。

解构struct

看个例子:

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

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

    let Point { x: a, y: b } = p;
    assert_eq!(0, a);
    assert_eq!(7, b);
}
  • Point结构体下有两个字段xy,都是i32类型
  • 有一个Point的实例叫p,其x字段值为0,y字段值为7
  • 然后使用模式对p进行解构,x的值被赋给ay的值被赋给了b

这么写还是有些冗杂,如果把a的变量名变为xb的变量名变为y,就可以简写为:

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

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

    let Point { x, y } = p;
    assert_eq!(0, x);
    assert_eq!(7, y);
}

解构还可以灵活地使用。看个例子:

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})");
        }
    }
}
  • 第一个分支要求x字段值随意,y字段值为0
  • 第二个分支要求x字段值为0,y字段值随意
  • 第三个分支对xy的值无要求

解构enum

看例子:

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 } => {
            println!("Move in the x direction {x} and in the y direction {y}");
        }
        Message::Write(text) => {
            println!("Text message: {text}");
        }
        Message::ChangeColor(r, g, b) => {
            println!("Change the color to red {r}, green {g}, and blue {b}")
        }
    }
}

该代码将打印 Change the color to red 0, green 160, and blue 255

解构嵌套的structenum

看例子:

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 color to red {r}, green {g}, and blue {b}");
        }
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println!("Change color to hue {h}, saturation {s}, value {v}")
        }
        _ => (),
    }
}

Message下的ChangeColor变体附带的数据就是Color枚举类型。使用match表达式匹配时一层一层匹配好即可。match的前两个分支外面都是ChangeColor变体,里面分别对应Color的两个变体,里面存的值都可以通过变量取出来。

解构structtuple

看例子:

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

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

main函数里的模式匹配外层是一个元组,这个元组有两个元素:

  • 第一个元素是个元组,里面有两个元素
  • 第二个是Point结构体

在模式中忽略值

有几种方式可以在模式中忽略整个值或部分值:

  • _:忽略整个值
  • _配合其他模式:忽略部分值
  • 使用以_开头的名称
  • ..:忽略值的剩余部分

使用_来忽略整个值

看例子:

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

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

这个代码将完全忽略作为第一个参数传递的值3 ,并打印This code only uses the y parameter: 4

使用嵌套的_来忽略值的一部分

看例子:

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:?}");

该代码将打印 Can't overwrite an existing customized value 进而 setting is Some(5) 。在第一个分支中,我们不需要匹配或使用Some变体中的值,但我们确实需要确定setting_valuenew_setting_valueSome变体。这就是忽略值的一部分。

第二个分支表示在所有其他情况下(如果setting_valuenew_setting_valueNone ),把new_setting_value变为setting_value 。这就是_配合其他模式来忽略某个值。

我们还可以在一种模式中的多个位置使用下划线来忽略特定值。看例子:

let numbers = (2, 4, 8, 16, 32);

match numbers {
    (first, _, third, _, fifth) => {
        println!("Some numbers: {first}, {third}, {fifth}")
    }
}

这里就忽略了元组的第2个和第4个元素。此代码将打印Some numbers: 2, 8, 32 ,并且值4和16将被忽略。

使用_开头命名来忽略未使用的变量

看例子:

fn main() {
    let _x = 5;
    let y = 10;
}

正常情况下如果你创建了变量但没有使用它Rust编译器会发出警告,_xy就没被使用,但使用y的警告。因为_x使用_开头告诉编译器这是个临时的变量。

请注意,仅使用_和使用以下划线开头的名称之间存在细微差别。语法_x仍然将值绑定到变量,而_根本不绑定。看例子:

let s = Some(String::from("Hello!"));

if let Some(_s) = s {
    println!("found a string");
}

println!("{s:?}");

我们会收到一个错误,因为s值仍会被移动到_s中,这会阻止我们打印s

对于这种情况,就应该使用_来避免绑定值的操作:

let s = Some(String::from("Hello!"));

if let Some(_) = s {
    println!("found a string");
}

println!("{s:?}");

使用..来忽略值的剩余部分

看例子:

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

fn main(){
	let origin = Point { x: 0, y: 0, z: 0 };

    match origin {
        Point { x, .. } => println!("x is {x}"),
    }
}

使用match匹配时之需要x字段就可以了,所以模式匹配只写x,其余用..

这么使用..也是可以的:

fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, .., last) => {
            println!("Some numbers: {first}, {last}");
        }
    }
}

只取开头和结尾的两个值,其余忽略。

这么写..是不行的:

fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (.., second, ..) => {
            println!("Some numbers: {second}")
        },
    }
}

前面是..,后面是..,我要中间的元素。但具体是哪个元素呢?这么写编译器不知道..具体要省略多少个元素,也就不明白second对应的是哪个元素。

输出:

$ cargo run
   Compiling patterns v0.1.0 (file:///projects/patterns)
error: `..` can only be used once per tuple pattern
 --> src/main.rs:5:22
  |
5 |         (.., second, ..) => {
  |          --          ^^ can only be used once per tuple pattern
  |          |
  |          previously used here

error: could not compile `patterns` (bin "patterns") due to 1 previous error

18.3.6. 使用match guards(match守卫)来提供额外的条件

match guardsmatch分支模式后一个附加的if条件,想要匹配该分支该条件也必须能满足。match guards适用于比单独的模式更复杂的场景。

看例子:

fn main(){
	let num = Some(4);

    match num {
        Some(x) if x % 2 == 0 => println!("The number {x} is even"),
        Some(x) => println!("The number {x} is odd"),
        None => (),
    }
}

match的第一个分支中,Some(x)是模式,而if x % 2 == 0就是match guards,要求Some附带的数据要能被2整除。

无法在模式中表达if x % 2 == 0条件,因此match guards使我们能够表达此逻辑。这种额外表达能力的缺点是,当涉及匹配保护表达式时,编译器不会尝试检查是否详尽。

看第二个例子:

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 = {x:?}, y = {y}");
}

此代码现在将打印Default case, x = Some(5)

match守卫if n == y不是模式,因此不会引入新变量。这个y是外部y(值为10)而不是新的阴影y ,我们可以通过比较来查找与外部y具有相同值的值 ny

看第三个例子:

    let x = 4;
    let y = false;

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

这个例子配合多重模式来使用match守卫。

匹配条件规定,仅当x的值等于456ytrue时,该分支才匹配。当此代码运行时,因为x4 ,但匹配守卫yfalse,所以不会执行第一个分支而会执行第二个分支输出no

这个例子里需要注意的是匹配模式相对于match守卫的优先级,其优先级应该是:

(4 | 5 | 6) if y => ...

而不是:

4 | 5 | (6 if y) => ...

18.3.7. @绑定

@符号让我们可以创建一个变量,该变量可以在测试某个值是否与模式匹配的同时保存该值。

看例子:

enum Message {
    Hello { id: i32 },
}

fn main(){
	let msg = Message::Hello { id: 5 };

    match msg {
        Message::Hello {
            id: id_variable @ 3..=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}"),
    }
}

这个例子match的第一个分支在模式匹配时既把id字段的值绑定在id_varible上又判断了其值应该在3到7的闭区间内。


http://www.kler.cn/a/528908.html

相关文章:

  • ReentrantReadWriteLock源码分析
  • 并发编程基础 - 并发编程的概念(C++)
  • gentoo 中更改$PS1
  • JavaScript原型链与继承:优化与扩展的深度探索
  • 【python】python基于机器学习与数据分析的手机特性关联与分类预测(源码+数据集)【独一无二】
  • LeetCode 跳跃类问题的解题技巧:跳跃游戏与最少跳跃数
  • 【漫话机器学习系列】073.黑塞矩阵(Hessian Matrix)
  • python算法和数据结构刷题[4]:查找算法和排序算法
  • Versal - 基础4(VD100+Versal IBERT)
  • C++解决输入空格字符串的三种方法
  • 智慧园区管理系统推动企业智能运维与资源优化的全新路径分析
  • 【Leetcode 热题 100】64. 最小路径和
  • 图书管理系统 Axios 源码__编辑图书
  • 增删改查(CRUD)操作
  • 新手从零开始使用飞牛fnOS搭建家庭数据管理中心体验NAS系统
  • pytorch基于 Transformer 预训练模型的方法实现词嵌入(tiansz/bert-base-chinese)
  • 【Linux】22.进程间通信(1)
  • webrtc编译需要常用环境变量以及相关名词解释
  • Leetcode::81. 搜索旋转排序数组 II
  • DRM系列三:drm core模块入口
  • 40. SPI实验
  • 《解锁AI黑科技:数据分类聚类与可视化》
  • 1979-2021年 全国各省、地级市、区县空气流通系数
  • Google Chrome-便携增强版[解压即用]
  • DeepSeek模型与OpenAI模型原理和技术架构的异同分析
  • 深度学习 Pytorch 神经网络的学习