枚举 Enum

enums give you a way of saying a value is one of a possible set of values.

定义枚举

#![allow(unused)]
fn main() {
enum IpAddrKind {
    V4,
    V6,
}
}

上面定义了一个ip地址类型的枚举,其中包含两个变体(variant)V4V6

枚举值

#![allow(unused)]
fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;
}

分别为枚举的两个变体创建了两个实例

注意使用枚举的变体同样使用::

#![allow(unused)]
fn main() {
fn route(ip_kind: IpAddrKind) {}

route(IpAddrKind::V4);
route(IpAddrKind::V6);
}

上面的route接收枚举IpAddrKind作为参数

#![allow(unused)]
fn main() {
    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }

    let home = IpAddr::V4(127, 0, 0, 1);

    let loopback = IpAddr::V6(String::from("::1"));
}

枚举的变体可以直接存放数据,而且每个变体可以是不同类型

#![allow(unused)]
fn main() {
struct Ipv4Addr {
    // --snip--
}

struct Ipv6Addr {
    // --snip--
}

enum IpAddr {
    V4(Ipv4Addr),
    V6(Ipv6Addr),
}
}

标准库std的ip地址实现

#![allow(unused)]
fn main() {
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

//上面枚举的4个变体类似于下面的4个结构体
//但使用结构体却不能简单的用一种函数参数传递上面所有的消息类型
struct QuitMessage; // unit struct
struct MoveMessage {
    x: i32,
    y: i32,
}
struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct


impl Message {
    fn call(&self) {
        // method body would be defined here
    }
}

let m = Message::Write(String::from("hello"));
m.call();
}

更复杂的枚举

  • Quit是一个空变体
  • Move类似带命名字段的结构体
  • WriteChangeColor类似元组结构体

类似结构体,枚举也可以在impl块中定义方法

Option Enum 及其相对Null的优点

rust中没有Null

#![allow(unused)]
fn main() {
enum Option<T> {
    None,
    Some(T),
}
}

rust通过标准库中的Option枚举来表达存在或者不存在(Null

因为Option枚举非常实用,所以已经预置不需要在明确引入作用域,不再需要Option::前缀直接使用SomeNone即可

#![allow(unused)]
fn main() {
    let some_number = Some(5);
    let some_char = Some('e');

    let absent_number: Option<i32> = None;
}

Some可以不必显式声明Option类型,可以通过Some传递的类型推断。 但None必须显式声明Option类型

为什么 Option 优于 Null

在你使用T之前,你必须处理Option<T>T的转换。这个过程通常能解决使用Null最常见的问题:当它实际为空时假设它不为空

当你需要一个可能为空的值时,必须明确使用Option<T>来表示这个值。在使用这个值时,必须明确的处理当这个值是Null的情况。这样 只要不是Option<T>的类型都可以安全的认为该值不会为Null

This was a deliberate design decision for Rust to limit null’s pervasiveness and increase the safety of Rust code.

match 流程控制结构

matchrust中非常强大的流程控制结构。可以允许你比较一个值和一系列的模式(pattern),然后执行匹配模式的代码

#![allow(unused)]
fn main() {
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        }
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

}

if不同的是if条件只接受bool值,而match模式可以接受任何类型

类似if,match的每一行也叫臂arm,arm=>分隔模式和代码,每行的arm,分隔

arm后代码是表达式;当arm后代码非常简单时不需要{},使用{},分隔可省略

绑定值的模式

#![allow(unused)]
fn main() {
#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
    Alabama,
    Alaska,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("State quarter from {state:?}!");
            25
        }
    }
}
}

当调用value_in_cents(Coin::Quarter(UsState::Alaska)),模式中的state会绑定到UsState::Alaska

与Option<T>匹配

#![allow(unused)]
fn main() {
    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);
}

match是全面的

matcharm的模式必须覆盖所有可能的值

#![allow(unused)]
fn main() {
    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            Some(i) => Some(i + 1),
        }
    }
}

以上代码会报错

error[E0004]: non-exhaustive patterns: None not covered

捕获所有模式和_占位符

#![allow(unused)]
fn main() {
    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        other => move_player(other),
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn move_player(num_spaces: u8) {}
}

最后一个armother可以覆盖所有可能的值,因此other不能出现在你指定的模式之前,否则你的模式不再会匹配,当然编译器也会用warn提醒你

#![allow(unused)]
fn main() {
    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _ => (), //注意这是一个空元组()也就是unit
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
}

当你不需要模式绑定值时,可以直接使用_占位符

模式匹配总结

  • rust模式匹配必须全面覆盖所有可能的值
  • rust有2种捕获所有模式(catch-all pattern)
    • 需要模式绑定值时使用other
    • 反之使用_

使用if let精简流程控制

#![allow(unused)]
fn main() {
    let config_max = Some(3u8);
    match config_max {
        Some(max) => println!("The maximum is configured to be {max}"),
        _ => (),
    }

//可使用if let简化为
    let config_max = Some(3u8);
    if let Some(max) = config_max {
        println!("The maximum is configured to be {max}");
    }
}

if let块只有在匹配到模式之后才会执行

选择if letmatch取决于你的具体场景和在简洁及全面模式覆盖之间的权衡。换言之,在只需要匹配一种模式忽略其他模式时将if let当作match的语法糖

也可添加else

#![allow(unused)]
fn main() {
    let mut count = 0;
    match coin {
        Coin::Quarter(state) => println!("State quarter from {state:?}!"),
        _ => count += 1,
    }

//if let简化
    let mut count = 0;
    if let Coin::Quarter(state) = coin {
        println!("State quarter from {state:?}!");
    } else {
        count += 1;
    }
}

总结

  1. rust标准库中的Option<T>有助于规避Null引起的问题
  2. 使用matchif let体验rust强大的模式匹配实现直观的流程控制