rust入坑之旅-枚举和模式匹配

Scroll Down

枚举

在数学理论中,一个集的枚举是列出某些有穷序列集的所有成员。 在计算机中更确切的可以表述为一类特定值得集合。枚举是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。学过Java看过海,玩了Rust不是海,在Java中枚举用enum来声明 在Rust中也是一样。

举个🌰收发快递

现在有一个场景 我们需要定义一个快递消息集合。有如下消息场景:快递站点转移需要设定发出站点和目的站点,包装需要增加快递单号,如需中途修改收货地址,则需要更改目的地消息,快递发出 快递到达等,常规的操作对我们而言则需要创建几个不同事件的结构体来标识是什么场景。

struct StartMessage;
struct FinishMessage;
struct MoveMessage {
    x: i32,
    y: i32,
}
struct WriteMessage(String);
impl WriteMessage {
    fn info(&self) {
        println!("快递📦单号:{}", self.0);
    }
}
struct ChangePointMessage(i32, i32, i32);

同样则可以使用一个枚举来定义所有的事件。并且相较于不同的结构体来说可以为一个枚举来增加关联方法即可,而不同的结构体则需要不同的impl块来标注关联方法,而枚举则可以轻易的定义一个能够处理这些不同类型的结构体的函数

#[derive(Debug)]
enum Message {
    Start,
    Finish,
    Move { x: i32, y: i32 },
    Write(String),
    ChangePoint(i32, i32, i32),
}

impl Message {
    fn info(&self) {
        // 在这里定义方法体
        println!("{:?}", self)
    }
}
 let m = Message::Write(String::from("112222222222"));
 m.call();
//output : Write("112222222222")


fn consume(event: Message) {
    match event {
        Message::Start => Send("快递发出"),
        Message::Finish => Send("快递已签收"),
        Message::ChangePoint(x, y, z) => Send("收货人'{}',更改收货地址 '{}'目标'{}'", x, y, z),
        Message::Write(s) => Send("快递📦单 \"{}\".", s),
        Message::Move { x, y } => {
            Send("快件在【{}】完成分拣,准备发往 【{}】", x, y);
        }
    }
}

举个🌰创建链表

enum List {
    Cons(u32, Box<List>),
    Nil,
}
impl List {
    fn new() -> List {
        Nil
    }
    fn prepend(self, elem: u32) -> List {
        Cons(elem, Box::new(self))
    }
    fn len(&self) -> u32 {
        match *self {
            Cons(_, ref tail) => 1 + tail.len(),
            Nil => 0,
        }
    }
    fn stringify(&self) -> String {
        match *self {
            Cons(head, ref tail) => {
                format!("{},{}", head, tail.stringify())
            }
            Nil => {
                format!("Nil")
            }
        }
    }
}
fn test_enum() {
    let mut list = List::new();
    // 追加一些元素
    list = list.prepend(1);
    list = list.prepend(2);
    list = list.prepend(3);

    // 显示链表的最后状态
    println!("linked list has length: {}", list.len());
    println!("{}", list.stringify());
}

Option 枚举和模式平匹配

Option 是标准库定义的另一个枚举。Option 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值,Java8的Optional和Rust的Option拥有一样的语义场景,都是包装一个值,被这个Option包装的值要么是值本身(需要解出来),要么就是没值。


/// The `Option` type. See [the module level documentation](self) for more.
#[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[rustc_diagnostic_item = "Option"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Option<T> {
    /// No value
    #[lang = "None"]
    #[stable(feature = "rust1", since = "1.0.0")]
    None,
    /// Some value `T`
    #[lang = "Some"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Some(#[stable(feature = "rust1", since = "1.0.0")] T),
}

类型 Option 表示一个可选值:每个 Option 均为 Some 并包含一个值,或者为 None,但不包含。 Option 类型在 Rust 代码中非常常见,因为它们有多种用途:

  • 初始值
  • 未在整个输入范围内定义的函数的返回值 (部分函数)
  • 返回值,用于报告否则将报告简单错误的错误,其中错误返回 None
  • 可选的结构体字段
  • 可借用或获取的结构体字段
  • 可选的函数参数
  • 处理空指针
  • 从不同的场景中取值配合高阶函数filter, map, map_or等转换值
fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    if denominator == 0.0 {
        None
    } else {
        Some(numerator / denominator)
    }
}

// 函数的返回值是一个Option枚举
let result = divide(2.0, 3.0);

// 模式匹配以获取值
match result {
    // 整除结果有效
    Some(x) => println!("Result: {}", x),
    // 整除结果无效
    None    => println!("Cannot divide by 0"),
}

Option方法

  • 处理引用
    as_ref 从 &Option 转换为 Option<&T>
    as_mut 从 &mut Option 转换为 Option<&mut T>
    as_deref 从 &Option 转换为 Option<&T::Target>
    as_deref_mut 从 &mut Option 转换为 Option<&mut T::Target>
    as_pin_ref 从 Pin<&Option> 转换为 Option<Pin<&T>>
    as_pin_mut 从 Pin<&mut Option> 转换为 Option<Pin<&mut T>>
  • 获取Option包装的源,如果 OptionNone
    expect panics 带有提供的自定义消息
    unwrap panics 带有泛型信息
    unwrap_or 返回提供的默认值
    unwrap_or_default 返回类型 T 的默认值 (必须实现 Default trait)
    unwrap_or_else 返回对提供的函数求值的结果
  • 转换Option包装的源,配合高阶函数
    • 转Result
      ok_or 使用提供的默认 err 值将 Some(v) 转换为 Ok(v),将 None 转换为 Err(err)
      ok_or_else 使用提供的函数将 Some(v) 转换为 Ok(v),并将 None 转换为 Err 的值
      transpose 实现 Result 和 Option 来回转换
    • 转换Option包装的源
      filter (过滤操作) 针对源过滤如果源不是None那么执行方法求值只为真返回源,为假返回一个None
      flatten 从一个对象中删除一层嵌套 Option<Option<T>>
      map (转换操作)通过将提供的函数应用于 Some 的包含值并保持 None 值不变,将 Option 转换为 Option<U>
      map_or 将提供的函数应用于 Some 的包含值,或者如果 Option 是返回提供的默认值 None
      map_or_else 转换Some的值并且提供默认值,直接返回被包装的值,等同Java8 Streamflatmap + get
    • 转换Option包装的源生成一个元组
      zip 把2个Some生成一个Some((x,y))
      zip_with 基于一个自定义函数来生成Some((x,y))
    • 布尔操作:这些方法将 Option 视为布尔值,其中 Some 的作用类似于 true,而 None 的作用类似于 false。
      image.png
      and_then 和 or_else 方法将函数作为输入,并且仅在需要产生新值时才评估函数。只有 and_then 方法可以生成具有与 Option 不同的内部类型 U 的 Option<U> 值。
      image.png
    • 归约操作:实现了 FromIterator trait,它允许将 Option 值上的迭代器收集到原始 Option 值的每个包含值的集合的 Option 中,或者如果任何元素是 None,则为 None。还实现了 ProductSum traits
      collect 收集到集合中
      product 求所有值得乘积
      sum 求和
    • 操作Option:原地修改Option枚举
      insert 插入一个值,丢弃任何旧内容
      get_or_insert 获取当前值并且插入一个新值。默认是None
      get_or_insert_default 获取当前值,如果是None,则插入类型 T (必须实现 Default) 的默认值
      get_or_insert_with 获取当前值,如果是None,则插入函数的返回值

布尔操作配map🌰

fn test_option_bool() {
    use std::collections::BTreeMap;
    let mut bt = BTreeMap::new();
    bt.insert(20u8, "foo");
    bt.insert(42u8, "bar");
    let res = vec![0u8, 1, 11, 200, 22]
        .into_iter()
        .map(|x| {
            // `checked_sub()` 出错时返回 `None` checked_sub方法判断Some(x)是否可以做减法
            x.checked_sub(1)
                // 与 `checked_mul()` 判断Some(x)是否可以做乘法
                .and_then(|x| x.checked_mul(2))
                // `BTreeMap::get` 出错时返回 `None`map获取参数
                .and_then(|x| bt.get(&x))
                // 兜底如果上述的布尔操作出现了错误则转向到or函数处理
                .or(Some(&"error!"))
                //拷贝一份
                .copied()
                // 获取Some的值 不会 panic 因为在or方法标注了`Some`
                .unwrap()
        })
        // 归约操作,收集到集合中
        .collect::<Vec<_>>();
    assert_eq!(res, ["error!", "error!", "foo", "error!", "bar"]);
}

模式匹配

Rust 通过 match 关键字来提供模式匹配,和其他语言的 switch 用法类似。第一个匹配分支会被比对,并且所有可能的值都必须被覆盖。

fn main() {
    let number = 13;
    // 试一试 ^ 将不同的值赋给 `number`

    println!("Tell me about {}", number);
    match number {
        // 匹配单个值
        1 => println!("One!"),
        // 匹配多个值
        2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
        // 试一试 ^ 将 13 添加到质数列表中
        // 匹配一个闭区间范围
        13..=19 => println!("A teen"),
        // 处理其他情况
        _ => println!("Ain't special"),
        // 试一试 ^ 注释掉这个总括性的分支
    }

    let boolean = true;
    // match 也是一个表达式
    let binary = match boolean {
        // match 分支必须覆盖所有可能的值
        false => 0,
        true => 1,
        // 试一试 ^ 将其中一条分支注释掉
    };

    println!("{} -> {}", boolean, binary);
}

match 代码块能以多种方式解构物元组,解构枚举,解构指针,解构结构体,解构结构体和枚举在上面的🌰中都有展示,解构元组如下展示。

fn main() {
    let triple = (0, -2, 3);
    // 试一试 ^ 将不同的值赋给 `triple`

    println!("Tell me about {:?}", triple);
    // match 可以解构一个元组
    match triple {
        // 解构出第二个和第三个元素
        (0, y, z) => println!("First is `0`, `y` is {:?}, and `z` is {:?}", y, z),
        (1, ..)  => println!("First is `1` and the rest doesn't matter"),
        // `..` 可用来忽略元组的其余部分
        _      => println!("It doesn't matter what they are"),
        // `_` 表示不将值绑定到变量
    }
}

解构指针和引用如下展示。

fn main() {
    // 获得一个 `i32` 类型的引用。`&` 表示取引用。
    let reference = &4;

    match reference {
        // 如果用 `&val` 这个模式去匹配 `reference`,就相当于做这样的比较:
        // `&i32`(译注:即 `reference` 的类型)
        // `&val`(译注:即用于匹配的模式)
        // ^ 我们看到,如果去掉匹配的 `&`,`i32` 应当赋给 `val`。
        // 译注:因此可用 `val` 表示被 `reference` 引用的值 4。
        &val => println!("Got a value via destructuring: {:?}", val),
    }

    // 如果不想用 `&`,需要在匹配前解引用。
    match *reference {
        val => println!("Got a value via dereferencing: {:?}", val),
    }

    // 如果一开始就不用引用,会怎样? `reference` 是一个 `&` 类型,因为赋值语句
    // 的右边已经是一个引用。但下面这个不是引用,因为右边不是。
    let _not_a_reference = 3;

    // Rust 对这种情况提供了 `ref`。它更改了赋值行为,从而可以对具体值创建引用。
    // 下面这行将得到一个引用。
    let ref _is_a_reference = 3;

    // 相应地,定义两个非引用的变量,通过 `ref` 和 `ref mut` 仍可取得其引用。
    let value = 5;
    let mut mut_value = 6;

    // 使用 `ref` 关键字来创建引用。
    // 译注:下面的 r 是 `&i32` 类型,它像 `i32` 一样可以直接打印,因此用法上
    // 似乎看不出什么区别。但读者可以把 `println!` 中的 `r` 改成 `*r`,仍然能
    // 正常运行。前面例子中的 `println!` 里就不能是 `*val`,因为不能对整数解
    // 引用。
    match value {
        ref r => println!("Got a reference to a value: {:?}", r),
    }

    // 类似地使用 `ref mut`。
    match mut_value {
        ref mut m => {
            // 已经获得了 `mut_value` 的引用,先要解引用,才能改变它的值。
            *m += 10;
            println!("We added 10. `mut_value`: {:?}", m);
        },
    }
}

卫语句 match 匹配中分支加if

fn main() {
    let pair = (2, -2);
    // 试一试 ^ 将不同的值赋给 `pair`

    println!("Tell me about {:?}", pair);
    match pair {
        (x, y) if x == y => println!("These are twins"),
        // ^ `if` 条件部分是一个卫语句
        (x, y) if x + y == 0 => println!("Antimatter, kaboom!"),
        (x, _) if x % 2 == 1 => println!("The first one is odd"),
        _ => println!("No correlation..."),
    }
}

使用@符号来咋match中实现变量绑定

// `age` 函数,返回一个 `u32` 值。
fn age() -> u32 {
    15
}

fn main() {
    println!("Tell me what type of person you are");

    match age() {
        0             => println!("I haven't celebrated my first birthday yet"),
        // 可以直接匹配(`match`) 1 ..= 12,但那样的话孩子会是几岁?
        // 相反,在 1 ..= 12 分支中绑定匹配值到 `n` 。现在年龄就可以读取了。
        n @ 1  ..= 12 => println!("I'm a child of age {:?}", n),
        n @ 13 ..= 19 => println!("I'm a teen of age {:?}", n),
        // 不符合上面的范围。返回结果。
        n             => println!("I'm an old person of age {:?}", n),
    }
}

相比于match支持的复杂通路,在简单分支场景中if let使用起来则更加简单, if let允许匹配枚举非参数化的变量,即枚举未注明 #[derive(PartialEq)],我们也没有为其实现 PartialEq


#![allow(unused)]
fn main() {
// 将 `optional` 定为 `Option<i32>` 类型
let optional = Some(7);

match optional {
    Some(i) => {
        println!("This is a really long string and `{:?}`", i);
        // ^ 行首需要 2 层缩进。这里从 optional 中解构出 `i`。
        // 译注:正确的缩进是好的,但并不是 “不缩进就不能运行” 这个意思。
    },
    _ => {},
    // ^ 必须有,因为 `match` 需要覆盖全部情况。不觉得这行很多余吗?
};



fn main() {
    // 全部都是 `Option<i32>` 类型
    let number = Some(7);
    let letter: Option<i32> = None;
    let emoticon: Option<i32> = None;

    // `if let` 结构读作:若 `let` 将 `number` 解构成 `Some(i)`,则执行
    // 语句块(`{}`)
    if let Some(i) = number {
        println!("Matched {:?}!", i);
    }

    // 如果要指明失败情形,就使用 else:
    if let Some(i) = letter {
        println!("Matched {:?}!", i);
    } else {
        // 解构失败。切换到失败情形。
        println!("Didn't match a number. Let's go with a letter!");
    };

    // 提供另一种失败情况下的条件。
    let i_like_letters = false;

    if let Some(i) = emoticon {
        println!("Matched {:?}!", i);
    // 解构失败。使用 `else if` 来判断是否满足上面提供的条件。
    } else if i_like_letters {
        println!("Didn't match a number. Let's go with a letter!");
    } else {
        // 条件的值为 false。于是以下是默认的分支:
        println!("I don't like letters. Let's go with an emoticon :)!");
    };
}
}

if let同样也有while let二者功能类似 后者主要在于处理循环中简化match处理

#![allow(unused)]
fn main() {
// 将 `optional` 设为 `Option<i32>` 类型
let mut optional = Some(0);

// 重复运行这个测试。
loop {
    match optional {
        // 如果 `optional` 解构成功,就执行下面语句块。
        Some(i) => {
            if i > 9 {
                println!("Greater than 9, quit!");
                optional = None;
            } else {
                println!("`i` is `{:?}`. Try again.", i);
                optional = Some(i + 1);
            }
            // ^ 需要三层缩进!
        },
        // 当解构失败时退出循环:
        _ => { break; }
        // ^ 为什么必须写这样的语句呢?肯定有更优雅的处理方式!
    }
}
}


fn main() {
    // 将 `optional` 设为 `Option<i32>` 类型
    let mut optional = Some(0);

    // 这读作:当 `let` 将 `optional` 解构成 `Some(i)` 时,就
    // 执行语句块(`{}`)。否则就 `break`。
    while let Some(i) = optional {
        if i > 9 {
            println!("Greater than 9, quit!");
            optional = None;
        } else {
            println!("`i` is `{:?}`. Try again.", i);
            optional = Some(i + 1);
        }
        // ^ 使用的缩进更少,并且不用显式地处理失败情况。
    }
    // ^ `if let` 有可选的 `else`/`else if` 分句,
    // 而 `while let` 没有。
}