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

文章目录

  • 4 流程控制
    • 4.1 if else表达式
    • 4.2 循环控制
      • 4.2.1 for循环
      • 4.2.2 while循环
      • 4.2.3 loop循环
  • 5 模式匹配
    • 5.1 match和if let
      • 5.1.1 match匹配
        • 使用match表达式赋值
        • 模式绑定
        • `_`通配符
      • 5.1.2 if let 匹配
      • 5.1.3 matches! 宏
    • 5.2 解构Option
    • 5.3 认识模式
        • match 分支
        • if let 分支
        • while let 条件循环
        • for 循环
        • let 语句
        • 函数参数
        • let 和 if let
    • 5.3 [全模式列表](https://course.rs/basic/match-pattern/all-patterns.html)

4 流程控制

4.1 if else表达式

if condition == true {
    // A...
} else {
    // B...
}

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

fn main() {
    let condition = true;
    let number = if condition {
        5
    } else {
        6
    };

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

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

4.2 循环控制

4.2.1 for循环

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

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

例如:

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

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

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

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

for item in &mut collection {
  // ...
}

总结如下:

使用方法 等价使用方式 所有权
for item in collection for item in IntoIterator::into_iter(collection) 转移所有权
for item in &collection for item in collection.iter() 不可变借用
for item in &mut collection for 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);
    }
}

两种循环方式:

// 第一种
let collection = [1, 2, 3, 4, 5];
for i in 0..collection.len() {
  let item = collection[i];
  // ...
}

// 第二种
for item in collection {

}

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

  • 性能:第一种使用方式中 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!("我出来了!");
}

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

以上代码当 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
}

该形式清晰的说明了何为模式,何为模式匹配:将模式与 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" 赋值给了 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,
                );
            }
        }
    }
}

运行后输出:

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

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

5.1.2 if let 匹配

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

    let v = Some(3u8);
    match v {
        Some(3) => println!("three"),
        _ => (),
    }

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

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

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

这两种匹配对于新手来说,可能有些难以抉择,但是只要记住一点就好:当你只要匹配一个条件,且忽略其他条件时就用 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
}

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

5.2 解构Option

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

enum Option<T> {
    Some(T),
    None,
}

简单解释就是:一个变量要么有值: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);

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

5.3 认识模式

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

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

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

match 分支
match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
}

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

match VALUE {
    PATTERN => EXPRESSION,
    PATTERN => EXPRESSION,
    _ => EXPRESSION,
}
if let 分支

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

if let PATTERN = SOME_VALUE {

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

这个例子会打印出 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);
}

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

let 语句
let PATTERN = EXPRESSION;

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

let x = 5;

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

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

上面将一个元组与模式进行匹配(模式和值的类型必需相同!),然后把 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

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

函数参数

函数参数也是模式:

fn foo(x: i32) {
    // 代码
}

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

fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({}, {})", x, y);
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}

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

let 和 if let

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

let Some(x) = some_option_value;

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

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

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

if let Some(x) = some_option_value {
    println!("{}", x);
}

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

5.3 全模式列表

你可能感兴趣的:(Rust,rust,开发语言,后端)