Rust-08-枚举和模式匹配

发布于:2024-06-09 ⋅ 阅读:(68) ⋅ 点赞:(0)

枚举类

结构体给予你将字段和数据聚合在一起的方法,像 Rectangle 结构体有 width 和 height 两
个字段。而枚举给予你将一个值成为一个集合之一的方法。比如,我们想让 Rectangle 是一
些形状的集合,包含 Circle 和 Triangle 。为了做到这个,Rust 提供了枚举类型。

enum BpmKindStatus {
    DirectManager,
    DeptManager,
}

fn main() {
    let status = DirectManager;

    chooseNextUserId(status);
}

fn chooseNextUserId(bpm_kind_status: BpmKindStatus) -> i32 {
    1
}

比如在bpm选择下个人的时候,根据不同的枚举类型选择不同的用户,实际使用中应该是用的数值型和字符串,我们可以使用一种更简洁的方式来表达相同的概念,仅仅使用枚举并将数据直接放进每一个枚举成员而不是将枚举作为结构体的一部分。
优化一下

enum BpmKindStatus {
    DirectManager(i32,String),
    DeptManager(i32,String),
}

fn main() {
    let status = DirectManager(10,String::from("部门领导"));

    chooseNextUserId(status);
}

fn chooseNextUserId(bpm_kind_status: BpmKindStatus) -> i32 {
    1
}

但是在正常的使用中肯定不是这样每个结构体都这样子定义的

struct QuitMessage; // 类单元结构体
struct MoveMessage {
 x: i32,
 y: i32,
}
struct WriteMessage(String); // 元组结构体
struct ChangeColorMessage(i32, i32, i32); // 元组结构体

enum Message {
 Quit,
 Move { x: i32, y: i32 },
 Write(String),
 ChangeColor(i32, i32, i32),
}

结构体和枚举还有另一个相似点:就像可以使用 impl 来为结构体定义方法那样,也可以在枚
举上定义方法。这是一个定义于我们 Message 枚举上的叫做 call 的方法:

impl Message {
 fn call(&self) {
 // 在这里定义方法体
 }
 }
fn main() {
    let m = Message::Write(String::from("hello"));
    m.call();
}

方法体使用了 self 来获取调用方法的值。这个例子中,创建了一个值为Message::Write(String::from(“hello”)) 的变量 m ,而且这就是当 m.call() 运行时 call 方法中的 self 的值。

让我们看看标准库中的另一个非常常见且实用的枚举:Option 。

Option 枚举和其相对于空值的优势

这一部分会分析一个 Option 的案例,Option 是标准库定义的另一个枚举。Option 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。没错,很简单的就联想到了java 的 Optional
标准库定义

enum Option<T> {
 None,
 Some(T),
}
fn main() {
 let some_number = Some(5);
 let some_char = Some('e');
 let absent_number: Option<i32> = None;
 }

总的来说,为了使用 Option 值,需要编写处理每个成员的代码。你想要一些代码只当拥有 Some(T) 值时运行,允许这些代码使用其中的 T 。也希望一些代码只在值为 None 时运行,这些代码并没有一个可用的 T 值。match 表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。

match控制流

可以把 match 表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会通过 match 的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

fn main() {}

这看起来非常像 if 所使用的条件表达式,不过这里有一个非常大的区别:对于 if ,表达式必须返回一个布尔值,而这里它可以是任何类型的。

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

绑定值的模式

#[derive(Debug)]
enum CoinType {
    YuanDaTou,
    Song,
    Qin,
    // --snip--
}
enum Coin {
    Penny,
    Nickel,
    Dime,
    Dynasty(CoinType)
}

fn coin_dynasty(coin:Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Dynasty(coinType) => {
            println!("coin from {:?}!", coinType);
            25
        }
    }
}
fn main() {
    coin_dynasty(Coin::Dynasty(CoinType::Song));
}

匹配option

如果我们想要编写一个函数,它获取一个 Option ,如果其中含有一个值,将其加一。如果其中没有值,函数应该返回 None 值,而不尝试执行任何操作。

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 与枚举相结合在很多场景中都是有用的。你会在 Rust 代码中看到很多这样的模式:match 一个枚举,绑定其中的值到一个变量,接着根据其值执行代码。

匹配是穷尽的

match 还有另一方面需要讨论:这些分支必须覆盖了所有的可能性。考虑一下 plus_one 函数
的这个版本,它有一个 bug 并不能编译:

fn plus_one(x: Option<i32>) -> Option<i32> {
 match x {
 Some(i) => Some(i + 1),
 }
 }

Rust 知道我们没有覆盖所有可能的情况甚至知道哪些模式被忘记了!Rust 中的匹配是 穷尽的:必须穷举到最后的可能性来使代码有效。特别的在这个 Option 的例子中,Rust 防止我们忘记明确的处理 None 的情况。

通配模式和 _ 占位符

通配模式类似于java switch中的default;

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) {} // 通配到了other里面
}

当没有变量时使用_

fn main() {
    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _=> retur(), // 如果这里不想运行任何方法,使用 _ => (),
    }
    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn retur() {} 
}

if let 简洁控制流

if let 语法让我们以一种不那么冗长的方式结合 if 和 let ,来处理只匹配一个模式的值而忽略其他模式的情况。
if let 语法获取通过等号分隔的一个模式和一个表达式。它的工作方式与 match 相同,这里的表达式对应 match 而模式则对应第一个分支。如果你的程序遇到一个使用 match 表达起来过于啰嗦的逻辑,那么可以使用 if let 语法

fn main() {
    let config_max = Some(3u8);
    if let Some(max) = config_max {
        println!("The maximum is configured to be {}", max);
    } else {
       // 处理其它逻辑
    }
}