• 【Rust基础②】流程控制、模式匹配


    4 流程控制

    4.1 if else表达式

    if condition == true {
        // A...
    } else {
        // B...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    该代码读作:若 condition 的值为 true,则执行 A 代码,否则执行 B 代码。例如:

    fn main() {
        let condition = true;
        let number = if condition {
            5
        } else {
            6
        };
    
        println!("The value of number is: {}", number);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • if 语句块是表达式,这里我们使用 if 表达式的返回值来给 number 进行赋值:number 的值是 5
    • if 来赋值时,要保证每个分支返回的类型一样(事实上,这种说法不完全准确,见这里),此处返回的 56 就是同一个类型,如果返回类型不一致就会报错

    可以将 else ififelse 组合在一起实现更复杂的条件分支判断

    4.2 循环控制

    4.2.1 for循环

    核心就在于 forin 的联动,语义表达如下:

    for 元素 in 集合 {
      // 使用元素干一些你懂我不懂的事情
    }
    
    • 1
    • 2
    • 3

    例如:

    fn main() {
        for i in 1..=5 {
            println!("{}", i);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    以上代码循环输出一个从 1 到 5 的序列。

    注意,使用 for 时我们往往使用集合的引用形式,除非你不想在后面的代码中继续使用该集合(比如我们这里使用了 container 的引用)。如果不使用引用的话,所有权会被转移(move)到 for 语句块中,后面就无法再使用这个集合了)

    如果想在循环中,修改该元素,可以使用 mut 关键字

    for item in &mut collection {
      // ...
    }
    
    • 1
    • 2
    • 3

    总结如下:

    使用方法等价使用方式所有权
    for item in collectionfor item in IntoIterator::into_iter(collection)转移所有权
    for item in &collectionfor item in collection.iter()不可变借用
    for item in &mut collectionfor item in collection.iter_mut()可变借用

    如果想在循环中获取元素的索引

    fn main() {
        let a = [4, 3, 2, 1];
        // `.iter()` 方法把 `a` 数组变成一个迭代器
        for (i, v) in a.iter().enumerate() {
            println!("第{}个元素是{}", i + 1, v);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    两种循环方式:

    // 第一种
    let collection = [1, 2, 3, 4, 5];
    for i in 0..collection.len() {
      let item = collection[i];
      // ...
    }
    
    // 第二种
    for item in collection {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    第一种方式是循环索引,然后通过索引下标去访问集合,第二种方式是直接循环集合中的元素,优劣如下:

    • 性能:第一种使用方式中 collection[index] 的索引访问,会因为边界检查(Bounds Checking)导致运行时的性能损耗 —— Rust 会检查并确认 index 是否落在集合内,但是第二种直接迭代的方式就不会触发这种检查,因为编译器会在编译时就完成分析并证明这种访问是合法的
    • 安全:第一种方式里对 collection 的索引访问是非连续的,存在一定可能性在两次访问之间,collection 发生了变化,导致脏数据产生。而第二种直接迭代的方式是连续访问,因此不存在这种风险

    使用 continue 可以跳过当前当次的循环,开始下次的循环;使用 break 可以直接跳出当前整个循环

    4.2.2 while循环

    如果你需要一个条件来循环,当该条件为 true 时,继续循环,条件为 false跳出循环,那么 while 就非常适用:

    fn main() {
        let mut n = 0;
    
        while n <= 5  {
            println!("{}!", n);
    
            n = n + 1;
        }
    
        println!("我出来了!");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4.2.3 loop循环

    loop 就是一个简单的无限循环,你可以在内部实现逻辑通过 break 关键字来控制循环何时结束。当使用 loop 时,必不可少的伙伴是 break 关键字,它能让循环在满足某个条件时跳出:

    fn main() {
        let mut counter = 0;
    
        let result = loop {
            counter += 1;
    
            if counter == 10 {
                break counter * 2;
            }
        };
    
        println!("The result is {}", result);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    以上代码当 counter 递增到 10 时,就会通过 break 返回一个 counter * 2 的值,最后赋给 result 并打印出来。

    这里有几点值得注意:

    • break 可以单独使用,也可以带一个返回值,有些类似 return
    • loop 是一个表达式,因此可以返回一个值

    5 模式匹配

    5.1 match和if let

    5.1.1 match匹配

    match 的通用形式:

    match target {
        模式1 => 表达式1,
        模式2 => {
            语句1;
            语句2;
            表达式2
        },
        _ => 表达式3
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    该形式清晰的说明了何为模式,何为模式匹配:将模式与 target 进行匹配,即为模式匹配

    • match 的匹配必须要穷举出所有可能,因此这里用 _ 来代表未列出的所有可能性
    • match 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同
    使用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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    因为这里匹配到 _ 分支,所以将 "::1" 赋值给了 ip_str

    模式绑定

    模式匹配的一个重要功能是从模式中取出绑定的值。

    模式绑定的例子:

    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,
                    );
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    运行后输出:

    $ 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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    _通配符

    match 的匹配必须穷尽所有情况,即穷尽匹配,Rust 编译器清晰地知道 match 中有哪些分支没有被覆盖, 这种行为能强制我们处理所有的可能性。

    通过将 _ 其放置于其他分支后,_ 将会匹配所有遗漏的值。() 表示返回单元类型与所有分支返回值的类型相同,所以当匹配到 _ 后,什么也不会发生。

    5.1.2 if let 匹配

    有时会遇到只有一个模式的值需要被处理,其它值直接忽略的场景,如果用 match 来处理就要写成下面这样:

        let v = Some(3u8);
        match v {
            Some(3) => println!("three"),
            _ => (),
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们只想要对 Some(3) 模式进行匹配, 不想处理任何其他 Some 值或 None 值。但是为了满足 match 表达式(穷尽性)的要求,写代码时必须在处理完这唯一的成员后加上 _ => (),这样会增加不少无用的代码。

    俗话说“杀鸡焉用牛刀”,我们完全可以用 if let 的方式来实现:

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

    这两种匹配对于新手来说,可能有些难以抉择,但是只要记住一点就好:当你只要匹配一个条件,且忽略其他条件时就用 if let ,否则都用 match

    无论是 match 还是 if let,他们都可以在模式匹配时覆盖掉老的值,绑定新的值:

    fn main() {
      let age=Some(30);
      let b=3;
      println!("匹配前b:{:?}",b); //3
      if let b=age{
          println!("b:{:?}",b); //Some(30)
      }
      println!("匹配后b:{:?}",b); //3
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    5.1.3 matches! 宏

    matches!宏可以将一个表达式跟模式进行匹配,然后返回匹配的结果 true or false

    例如:

    let foo = 'f';
    assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
    
    let bar = Some(4);
    assert!(matches!(bar, Some(x) if x > 2));
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.2 解构Option

    Opention枚举用来解决Rust中变量是否有值的问题,定义如下:

    enum Option<T> {
        Some(T),
        None,
    }
    
    • 1
    • 2
    • 3
    • 4

    简单解释就是:一个变量要么有值:Some(T), 要么为空:None

    使用 Option,是为了从 Some 中取出其内部的 T 值以及处理没有值的情况,为了演示这一点,下面一起来编写一个函数,它获取一个 Option,如果其中含有一个值,将其加一;如果其中没有值,则函数返回 None 值:

    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    plus_one 接受一个 Option 类型的参数,同时返回一个 Option 类型的值(这种形式的函数在标准库内随处所见),在该函数的内部处理中,如果传入的是一个 None ,则返回一个 None 且不做任何处理;如果传入的是一个 Some(i32),则通过模式绑定,把其中的值绑定到变量 i 上,然后返回 i+1 的值,同时用 Some 进行包裹。

    5.3 认识模式

    模式是 Rust 中的特殊语法,它用来匹配类型中的结构和数据,模式一般由以下内容组合而成:

    • 字面值
    • 解构的数组、枚举、结构体或者元组
    • 变量
    • 通配符
    • 占位符

    所有可能认识到模式的地方:

    match 分支
    match VALUE {
        PATTERN => EXPRESSION,
        PATTERN => EXPRESSION,
        PATTERN => EXPRESSION,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如上所示,match 的每个分支就是一个模式,因为 match 匹配是穷尽式的,因此我们往往需要一个特殊的模式 _,来匹配剩余的所有情况:

    match VALUE {
        PATTERN => EXPRESSION,
        PATTERN => EXPRESSION,
        _ => EXPRESSION,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    if let 分支

    if let 往往用于匹配一个模式,而忽略剩下的所有模式的场景:

    if let PATTERN = SOME_VALUE {
    
    }
    
    • 1
    • 2
    • 3
    while let 条件循环

    一个与 if let 类似的结构是 while let 条件循环,它允许只要模式匹配就一直进行 while 循环。下面展示了一个使用 while let 的例子:

    // 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这个例子会打印出 32 接着是 1pop 方法取出动态数组的最后一个元素并返回 Some(value),如果动态数组是空的,将返回 None,对于 while 来说,只要 pop 返回 Some 就会一直不停的循环。一旦其返回 Nonewhile 循环停止。可以使用 while let 来弹出栈中的每一个元素。

    for 循环
    let v = vec!['a', 'b', 'c'];
    
    for (index, value) in v.iter().enumerate() {
        println!("{} is at index {}", value, index);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里使用 enumerate 方法产生一个迭代器,该迭代器每次迭代会返回一个 (索引,值) 形式的元组,然后用 (index,value) 来匹配。

    let 语句
    let PATTERN = EXPRESSION;
    
    • 1

    是的, 该语句我们已经用了无数次了,它也是一种模式匹配:

    let x = 5;
    
    • 1

    这其中,x 也是一种模式绑定,代表将匹配的值绑定到变量 x 上。因此,在 Rust 中,变量名也是一种模式,只不过它比较朴素很不起眼罢了。

    let (x, y, z) = (1, 2, 3);
    
    • 1

    上面将一个元组与模式进行匹配(模式和值的类型必需相同!),然后把 1, 2, 3 分别绑定到 x, y, z 上。

    模式匹配要求两边的类型必须相同,否则就会导致下面的报错:

    let (x, y) = (1, 2, 3);
    error[E0308]: mismatched types
     --> src/main.rs:4:5
      |
    4 | let (x, y) = (1, 2, 3);
      |     ^^^^^^   --------- this expression has type `({integer}, {integer}, {integer})`
      |     |
      |     expected a tuple with 3 elements, found one with 2 elements
      |
      = note: expected tuple `({integer}, {integer}, {integer})`
                 found tuple `(_, _)`
    For more information about this error, try `rustc --explain E0308`.
    error: could not compile `playground` due to previous error
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    对于元组来说,元素个数也是类型的一部分!

    函数参数

    函数参数也是模式:

    fn foo(x: i32) {
        // 代码
    }
    
    • 1
    • 2
    • 3

    其中 x 就是一个模式,你还可以在参数中匹配元组:

    fn print_coordinates(&(x, y): &(i32, i32)) {
        println!("Current location: ({}, {})", x, y);
    }
    
    fn main() {
        let point = (3, 5);
        print_coordinates(&point);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    &(3, 5) 会匹配模式 &(x, y),因此 x 得到了 3y 得到了 5

    let 和 if let

    对于以下代码,编译器会报错:

    let Some(x) = some_option_value;
    
    • 1

    因为右边的值可能不为 Some,而是 None,这种时候就不能进行匹配,也就是上面的代码遗漏了 None 的匹配。

    类似 let , formatch 都必须要求完全覆盖匹配,才能通过编译( 不可驳模式匹配 )。

    但是对于 if let,就可以这样使用:

    if let Some(x) = some_option_value {
        println!("{}", x);
    }
    
    • 1
    • 2
    • 3

    因为 if let 允许匹配一种模式,而忽略其余的模式( 可驳模式匹配 )。

    5.3 全模式列表

  • 相关阅读:
    Jmeter压测报错:java.net.SocketException: Socket closed
    统信UOS桌面操作系统安装教程
    Spring复习——day17_SpringMVC_执行流程
    【纯手工打造】时间戳转换工具(python)
    5. JAVA 基础
    低代码在物品领用领域数字化转型的案例分析
    linux解压文件命令
    从 @SpringBootApplication 入手,理解 Spring 注解驱动编程
    蓄电池智能监控系统,蓄电池智能监控系统的作用
    cnpm安装步骤
  • 原文地址:https://blog.csdn.net/qq_51601649/article/details/133762531