rust学习教程22-全模式列表(代码片段)

孙飞Sunface 孙飞Sunface     2022-12-13     113

关键词:

本文节选自<<Rust语言圣经>>一书
欢迎大家加入Rust编程学院,一起学习交流:
QQ群:1009730433

全模式列表

在本书中我们已领略过许多不同类型模式的例子. 本节的目标就是把这些模式语法都罗列出来,方便大家检索查阅。

匹配字面值

let x = 1;

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

这段代码会打印 one 因为 x 的值是 1。如果希望代码获得特定的具体值,则该语法很有用。

匹配命名变量

match一章中,我们有讲过变量覆盖的问题,这个在匹配命名变量时会遇到:

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

让我们看看当 match 语句运行的时候发生了什么。第一个匹配分支的模式并不匹配 x 中定义的值,所以代码继续执行。

第二个匹配分支中的模式引入了一个新变量 y,它会匹配任何 Some 中的值。因为我们在 match 表达式的新作用域中,这是一个新变量,而不是开头声明为值 10 的那个 y。这个新的 y 绑定会匹配任何 Some 中的值,在这里是 x 中的值。因此这个 y 绑定了 xSome 内部的值。这个值是 5,所以这个分支的表达式将会执行并打印出 Matched, y = 5

如果 x 的值是 None 而不是 Some(5),头两个分支的模式不会匹配,所以会匹配模式_。这个分支的模式中没有引入变量 x,所以此时表达式中的 x 会是外部没有被覆盖的 x

一旦 match 表达式执行完毕,其作用域也就结束了,同理内部 y 的作用域也结束了。最后的 println! 会打印 at the end: x = Some(5), y = 10

如果你不想引入变量覆盖,那么需要使用匹配守卫(match guard)的方式,稍后在匹配守卫提供的额外条件中会讲解。

单分支多模式

match 表达式中,可以使用 | 语法匹配多个模式,它代表 的意思。例如,如下代码将 x 的值与匹配分支相比较,第一个分支有 选项,意味着如果 x 的值匹配此分支的任何一个模式,它就会运行:

let x = 1;

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

上面的代码会打印 one or two

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

数值类型中我们有讲到一个序列语法,该语言不仅可以用循环中,还能用于匹配模式。

..= 语法允许你匹配一个闭区间序列内的值。在如下代码中,当模式匹配任何在此序列内的值时,该分支会执行:

let x = 5;

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

如果 x 是 1、2、3、4 或 5,第一个分支就会匹配。这相比使用 | 运算符表达相同的意思更为方便;相比 1..=5,使用 | 则不得不指定 1 | 2 | 3 | 4 | 5。相反指定序列就简短的多,特别是在希望匹配比如从 1 到 1000 的数字的时候!

序列只允许用于数字或字符类型,因为编译器会在编译时检查序列不为空。字符和数字值是 Rust 仅有的可以判断范围是否为空的类型。

如下是一个使用字符类型序列的例子:

let x = 'c';

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

Rust 知道 c 位于第一个模式的序列内,并会打印出 early ASCII letter

解构并分解值

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

解构结构体

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

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

这段代码创建了变量 ab 来匹配结构体 p 中的 xy 字段, 这个例子展示了模式中的变量名不必与结构体中的字段名一致。不过通常希望变量名与字段名一致以便于理解变量来自于哪些字段。

因为变量名匹配字段名是常见的,同时因为 let Point x: x, y: y = p; 包含了很多重复,所以对于匹配结构体字段的模式存在简写:只需列出结构体字段的名称,则模式创建的变量会有相同的名称。下例与上例有着相同行为的代码,不过 let 模式创建的变量为 xy 而不是 ab

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

这段代码创建了变量 xy,与结构体p 中的 xy字段相匹配。其结果是变量 xy 包含结构体 p 中的值。

也可以使用字面值作为结构体模式的一部分进行进行解构,而不是为所有的字段创建变量。这允许我们测试一些字段为特定值的同时创建其他字段的变量。

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

# struct Point 
#     x: i32,
#     y: i32,
# 
#
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第一个分支,指定匹配y0Point
然后第二个分支在第一个分支之后,匹配y不为0x0Point;
最后一个分支匹配x不为0y也不为0Point.

在这个例子中,值 p 因为其 x 包含 0 而匹配第二个分支,因此会打印出 On the y axis at 7

解构枚举

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

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

这里老生重提一句话,模式匹配一样要类型相同,因此匹配Message::Move1,2这样的枚举值,就必须要用Message::Movex,y这样的同类型模式才行。

这段代码会打印出 Change the color to red 0, green 160, and blue 255。尝试改变 msg 的值来观察其他分支代码的运行。

对于像 Message::Quit 这样没有任何数据的枚举成员,不能进一步解构其值。只能匹配其字面值 Message::Quit,因此模式中没有任何变量。

对于另外两个枚举成员,就用相同类型的模式去匹配出对应的值即可。

解构嵌套的结构体和枚举

目前为止,所有的例子都只匹配了深度为一级的结构体或枚举。当然也可以匹配嵌套的项!

例如使用下面的代码来同时支持 RGB 和 HSV 色彩模式:

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
            )
        
        _ => ()
    

match第一个分支的模式匹配一个Message::ChangeColor枚举成员,该枚举成员又包含了一个Color::Rgb的枚举成员,最终绑定了3个内部的i32值。第二个,就交给亲爱的读者来思考完成。

解构结构体和元组

甚至可以用复杂的方式来混合、匹配和嵌套解构模式。如下是一个复杂结构体的例子,其中结构体和元组嵌套在元组中,并将所有的原始类型解构出来:

struct Point 
     x: i32,
     y: i32,
 

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

这种将复杂类型分解匹配的方式,可以让我们单独得到感兴趣的值。

忽略模式中的值

有时忽略模式中的一些值是很有用的,比如在match中的最后一个分支使用_模式匹配剩余的所有值。 你也可以在另一个模式中使用 _ 模式,使用一个以下划线开始的名称,或者使用 .. 忽略所剩部分的值。

使用 _ 忽略整个值

虽然 _ 模式作为 match 表达式最后的分支特别有用,但是我们可以让它更有用。例如可以将其用于函数参数中:

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中的值,直接进行忽略。
剩下的形如(Some(_),None)(None, Some(_)), (None,None)形式,都由第二个分支_进行分配。

还可以在一个模式中的多处使用下划线来忽略特定值,如下所示,这里忽略了一个五元元组中的第二和第四个值:

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

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

老生常谈:模式匹配一定要类型相同,因此匹配numbers元组的模式,也必须有五个值。

这会打印出 Some numbers: 2, 8, 32, 值 4 和 16 会被忽略。

使用下划线开头忽略未使用的变量

如果你创建了一个变量却不在任何地方使用它, Rust 通常会给你一个警告,因为这可能会是个 bug。但是有时创建一个还未使用的变量是有用的,比如你正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头:

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

这里得到了警告说未使用变量 y,至于x则并没有警告。

注意, 只使用 _ 和使用以下划线开头的名称有些微妙的不同:比如 _x 仍会将值绑定到变量,而 _ 则完全不会绑定

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

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


println!(":?", s);

s是一个拥有所有权的动态字符串,在上面代码中,我们会得到一个错误,因为 s 的值会被转移给 _s, 在println!中再次使用s会报错:

error[E0382]: borrow of partially moved value: `s`
 --> src/main.rs:8:22
  |
4 |     if let Some(_s) = s 
  |                 -- value partially moved here
...
8 |     println!(":?", s);
  |                      ^ value borrowed here after partial move

只使用下划线本身,则并不会绑定值,因为 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,


let origin = Point  x: 0, y: 0, z: 0 ;

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

这里列出了 x 值,接着使用了.. 模式来忽略其它字段,这样的写法要比一一列出其它字段,然后用_忽略简洁的多。

还可以用..来忽略中间的所有值:

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

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

这里用 firstlast 来匹配第一个和最后一个值。.. 将匹配并忽略中间的所有值。

然而使用 .. 必须是无歧义的。如果期望匹配和忽略的值是不明确的,Rust 会报错。下面代码展示了一个带有歧义的 .. 例子,因此不能编译:

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

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

如果编译上面的例子,会得到下面的错误:

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 `world_hello` due to previous error              ^^

Rust无法判断,second应该匹配numbers中的第几个元素,因此在这里使用两个..模式,是由很大歧义的!

匹配守卫提供的额外条件

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

这个条件可以使用模式中创建的变量:

let num = Some(4);

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

上例会打印出 less than five: 4。当 num 与模式中第一个分支比较时,因为 Some(4) 匹配 Some(x) 所以可以匹配。接着匹配守卫检查 x 值是否小于 5,因为 4 小于 5,所以第一个分支被选择。

相反如果 numSome(10),因为 10 不小于 5 所以第一个分支的匹配守卫为假。接着 Rust 会前往第二个分支,这会匹配因为它没有匹配守卫所以会匹配任何 Some 成员。

因为模式中无法提供类如if x < 5的表达能力,所以我们通过匹配守卫的方式来实现。

在之前, 我们提到可以使用匹配守卫来解决模式中变量覆盖的问题,那里 match 表达式的模式中新建了一个变量而不是使用 match 之外的同名变量。新变量意味着不能够测试外部变量的值,下面代码展示了如何使用匹配守卫修复这个问题。

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

现在这会打印出 Default case, x = Some(5)。现在第二个匹配分支中的模式不会引入一个覆盖外部 y 的新变量 y,这意味着可以在匹配守卫中使用外部的 y。相比指定会覆盖外部 y 的模式 Some(y),这里指定为 Some(n)。此新建的变量 n 并没有覆盖任何值,因为 match 外部没有变量 n

匹配守卫 if n == y 并不是一个模式所以没有引入新变量。这个 y 正是 外部的 y 而不是新的覆盖变量 y,这样就可以通过比较 ny 来表达寻找一个与外部 y 相同的值的概念了。

也可以在匹配守卫中使用 运算符 | 来指定多个模式,同时匹配守卫的条件会作用于所有的模式。下面代码展示了结合匹配守卫与使用了 | 的模式的优先级。这个例子中重要的部分是匹配守卫 if y 作用于 45 6,即使这看起来好像 if y 只作用于 6

let x = 4;
let y = false;

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

这个匹配条件表明此分支值匹配 x 值为 456 同时 ytrue 的情况。

虽然在第一个分支中,x匹配了模式4,但是对于匹配守卫if y来说,因为yfalse,因此该守卫条件的值永远是false,也意味着第一个分支永远无法被匹配.

下面的文字图解释了匹配守卫作用于多个模式时的优先级规则,第一张是正确的:

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

而第二张图是错误的

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

可以通过运行代码时的情况看出这一点:如果匹配守卫只作用于由 | 运算符指定的值列表的最后一个值,这个分支就会匹配且程序会打印出 yes

@ 绑定

@(读作at)运算符允许为一个字段绑定另外一个变量。下面例子中,我们希望测试 Message::Helloid 字段是否位于 3..=7 范围内,同时也希望能将其值绑定到 id_variable 变量中以便此分支相关联的代码可以使用它。可以将 id_variable 命名为 id,与字段同名,不过出于示例的目的这里选择了不同的名称。

rust语言圣经22-全模式列表(代码片段)

...Rust学习社区官网:https://college.rsQQ群:1009730433全模式列表在本书中我们已领略过许多不同类型模式的例子.本节的目标就是把这些模式语法都罗列出来,方便大家检索查阅。匹配字面值letx=1;matchx1=>println!("on... 查看详情

rust学习教程21-option和模式匹配(代码片段)

本文节选自<<Rust语言圣经>>一书欢迎大家加入Rust编程学院,一起学习交流:QQ群:1009730433解构Option在枚举那一章,提到过Option枚举变量是用来解决Rust中一个变量是否有值的问题,定义如下:enumOption<T>Som... 查看详情

rust学习-result/option/unwrap/?(代码片段)

...然Rust中有null的概念,但是使用null并不是Rust中常见的模式。假设我们要写一个函数,输入 查看详情

一天一门编程语言rust语言程序设计极简教程(代码片段)

...型的语法泛型的应用TraitTrait的定义Trait的实现Trait的继承模式匹配模式匹配的语法模式匹配的特性Rust语言模块和结构体什么是结构体实例化结构体结构体的方法结构体的模式匹配结构体的作用Rust语言智能指针一、Rust中什么是智... 查看详情

rust学习教程21-option和模式匹配(代码片段)

本文节选自<<Rust语言圣经>>一书欢迎大家加入Rust编程学院,一起学习交流:QQ群:1009730433解构Option在枚举那一章,提到过Option枚举变量是用来解决Rust中一个变量是否有值的问题,定义如下:enumOption<T>Som... 查看详情

每天一道rust-leetcode(2019-06-11)(代码片段)

每天一道Rust-LeetCode(2019-06-02)Z字形变换坚持每天一道题,刷题学习Rust.题目描述全排列II给定一个可包含重复数字的序列,返回所有不重复的全排列。示例:输入:[1,1,2]输出:[[1,1,2],[1,2,1],[2,1,1]]解题过程思路:因为要穷举结果,所以只能... 查看详情

rust学习教程26-特征对象(代码片段)

本文节选自<<Rust语言圣经>>一书欢迎大家加入Rust编程学院,一起学习交流:QQ群:1009730433特征对象在上一节中有一段代码无法通过编译:fnreturns_summarizable(switch:bool)->implSummaryifswitchPost//...elseWeibo//...其中Post和Weibo... 查看详情

算法leetcode890.查找和替换模式(rust和go的性能是真的好)(代码片段)

文章目录890.查找和替换模式:样例1:提示:分析题解rustgoc++javapython原题传送门:https://leetcode.cn/problems/find-and-replace-pattern/890.查找和替换模式:你有一个单词列表words和一个模式pattern,你想知道wo 查看详情

尚硅谷设计模式学习(22)---[状态模式(statepattern)](代码片段)

尚硅谷传送门==>B站尚硅谷Java设计模式❤❤❤感谢尚硅谷❤❤❤最近开始计划学习一下设计模式了,加油!!!状态模式来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为一一对应,状态之间... 查看详情

rust学习教程28-深入类型转换(代码片段)

本文节选自<<Rust语言圣经>>一书欢迎大家加入Rust编程学院,一起学习交流:QQ群:1009730433类型转换Rust是类型安全的语言,因此在Rust中做类型转换不是一件简单的事,这一章节我们将对Rust中的类型转换进行... 查看详情

rust学习教程26-特征对象(代码片段)

本文节选自<<Rust语言圣经>>一书欢迎大家加入Rust编程学院,一起学习交流:QQ群:1009730433特征对象在上一节中有一段代码无法通过编译:fnreturns_summarizable(switch:bool)->implSummaryifswitchPost//...elseWeibo//...其中Post和Weibo... 查看详情

rust学习教程26-特征对象(代码片段)

本文节选自<<Rust语言圣经>>一书欢迎大家加入Rust编程学院,一起学习交流:QQ群:1009730433特征对象在上一节中有一段代码无法通过编译:fnreturns_summarizable(switch:bool)->implSummaryifswitchPost//...elseWeibo//...其中Post和Weibo... 查看详情

rust学习(代码片段)

Rust  1.install  2.playonline  curlhttps://sh.rustup.rs-sSf|shecho‘PATH="$PATH:$HOME/.cargo/bin"‘>>~/.bashrcrustupdoc Tutorialorglearn (entry)   查看详情

rust学习教程14-函数(代码片段)

本文节选自<<Rust语言圣经>>一书欢迎大家加入Rust编程学院,一起学习交流:QQ群:1009730433函数Rust的函数我们在之前已经见过不少,跟其他语言几乎没有什么区别。因此本章的学习之路将轻松和愉快,骚年... 查看详情

rust学习教程14-函数(代码片段)

本文节选自<<Rust语言圣经>>一书欢迎大家加入Rust编程学院,一起学习交流:QQ群:1009730433函数Rust的函数我们在之前已经见过不少,跟其他语言几乎没有什么区别。因此本章的学习之路将轻松和愉快,骚年... 查看详情

rust学习1(代码片段)

1.为什么学习rust官网的解释如下,性能好,可靠,生产力。我的理由,想看看怎么保证内存安全与线程安全的 1.环境搭建去网站下载对应的rustup-init   https://forge.rust-lang.org/infra/other-installation-methods.html然后按照操作... 查看详情

dba学rust设计模式--观察者模式(代码片段)

#[derive(Debug,Clone)]structWork//消息实体,存储消息id:i32structWorkMaster//消息管理者属性workers:Vec<Box<Worker>>//存储注册的接收者。每个接收者须实现Worker接口才能注册进。implWorkMasterfnregister_worker(&mutself,worker: 查看详情

rust学习教程27-深入了解特征(代码片段)

本文节选自<<Rust语言圣经>>一书欢迎大家加入Rust编程学院,一起学习交流:QQ群:1009730433深入了解特征特征之于Rust更甚于接口之于其他语言,因此特征在Rust中很重要也相对较为复杂,我们决定把特征分为... 查看详情