【Rust】第七节:枚举与模式匹配

0 前言

是我的一点学习笔记,因为本身内容就不多、不复杂,所以这一篇内容结构与原文基本一致,但是是我个人理解原文的一个思路过程的记录。

枚举,enums,如果你了解tspythonc/cppjava那你可能会觉得很熟悉,但是又很不同,rust的枚举更丰富、更灵活、更方便、更强大。
所以你准备走进rust的枚举类型了吗?

原文链接:Rust程序设计语言


1 枚举

rust有两种枚举,一个是enum,一个是option,我们一个一个来看。

1.1 enum

以IPv4和IPv6为例

// 以下是伪代码,不可直接运行
// 1 定义enum
enum IpAddrKind {
   
    V4,
    V6,
}
// 2 实例化enum
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
// 3 函数可以以enum为入参
fn route(ip_type: IpAddrKind) {
    }
// 4 使用函数
route(IpAddrKind::V4);
route(IpAddrKind::V6);

以上是最简单的写法,那么在此之上,我们是否可以将enum与struct结合起来,从而实现更加复杂的enum类型呢?显然是可以的

enum IpAddrKind {
   
    V4,
    V6,
}
struct IpAddr {
   
    kind: IpAddrKind,
    address: String,
}
let home = IpAddr {
   
    kind: IpAddrKind::V4,
    address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
   
    kind: IpAddrKind::V6,
    address: String::from("::1"),
};

而事实上,我们没有必要把枚举作为结构体的一部分,而是可以直接给枚举成员赋值,就像下面这样

enum IpAddr {
   
    V4(String),
    V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));

而更加有趣的是,enum可以存储不同的类型!比如像这样

enum IpAddr {
   
    V4(u8, u8, u8, u8),
    V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));

实际上,标准库也提供了一个开箱即用的定义,让我们来看看学习一下——

struct Ipv4Addr {
   
    // --snip--
}
struct Ipv6Addr {
   
    // --snip--
}
enum IpAddr {
   
    V4(Ipv4Addr),
    V6(Ipv6Addr),
}

既然enum能存储不同的类型,那自然也能存储不同的struct,这种方式大大地拓展了enum的灵活性和可用性
还有更有趣的!在上一节中,我们说了struct可以使用impl,同样的,enum也能使用imple,这意味着你能实现这样的效果——

enum Message {
   
    Quit,
    Move {
    x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
impl Message {
   
    fn call(&self) {
   
        // 在这里定义方法体
    }
}
let m = Message::Write(String::from("hello"));
m.call();

1.2 option

optionrust提供的另一种枚举值,为什么要option?是为了解决空值(null)问题。
你可能在其他语言的开发中,经常遇到空值引起的各种问题。而rust中,没有空值,所以不会有空值问题。
但是rust提供了一个可以编码存在或不存在的概念的枚举,这是如何实现的呢?让我们看看标准库——

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

1、这里的<T>是泛型语法,你可能在别的语言中已经接触过了,这里先不展开。
2、Some就是,存在一个值;None,就是并没有一个有效的值,与空值起到同样的作用。
3、Option<T><T>是不同的类型!所以他们不允许像对一个有效的<T>那样处理Option<T>,也就避免了问题。
是不是听起来有点拗口?让我们看看这个例子

let x: i8 = 5;
let y: Option<i8> = Some(5);

let sum = x + y;// 编译器报错

是的,通过此种方式,避免了我们把一个无效的值当成一个有效的值去处理,所以我们可以安全地处理值,并且信赖他绝对不会是空值!而且,在你处理这个值时,需要显式地生命当他为空值的时候的处理方式。
于是乎,通过这种有效值、无效值的枚举方式,实现了使用、判断等场景下的安全性。
这里提到了“判断”,接下来我们就要讲match表达式了。


2 match

match是一个控制流运算符。比如说——

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, // 必须穷尽所有的可能的处理
    }
}

是不是有点像c/cpp的switch语法?让我们问问chatgpt吧,他说:
1、强大的模式匹配:match支持强大的模式匹配包括结构体、枚举、引用等等;
2、更好的安全性:match必须处理所有可能的情况;
3、更好的表达能力:match可以返回值

好的,那让我们继续看看这些神奇的点吧
模式匹配,还有一个功能就是可以绑定匹配的模式的部分值,也就是可以从枚举成员中,提取出值来使用

#[derive(Debug)] // 这样可以立刻看到州的名称
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) => {
    // 增加state变量 
            println!("State quarter from {:?}!", state);
            25
        } // 像这样,就可以获取coin的quarter成员中的,内部的值
    }
}

上面说的,都是enum,不要忘了我们的option哦,通过match模式的必须穷举处理的特性,避免了遗漏空值处理场景的编码问题

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

另外,还有统配模式与_占位符可以使用,要注意的是,other意味着你需要使用这个变量值,而如果不需要变量值你可以使用_

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) {
   }
let dice_roll = 9;
match dice_roll {
   
    3 => add_fancy_hat(),
    7 => remove_fancy_hat(),
    _ => reroll(), // 如果不需要使用变量值
    // _ => (), // 如果不需要处理
}
fn add_fancy_hat() {
   }
fn remove_fancy_hat() {
   }
fn reroll() {
   }

3 if let

if let是更简单的控制流,没有什么好说的,直接看代码就行,比如以下两种方式,效果是相同的——

let mut count = 0;
match coin {
   
    Coin::Quarter(state) => println!("State quarter from {:?}!", state),
    _ => count += 1,
}
let mut count = 0;
if let Coin::Quarter(state) = coin {
   
    println!("State quarter from {:?}!", state);
} else {
   
    count += 1;
}

相关推荐

  1. Rust模式匹配

    2024-01-30 17:08:02       70 阅读
  2. 深入Rust模式匹配类型

    2024-01-30 17:08:02       53 阅读
  3. Rust-08-模式匹配

    2024-01-30 17:08:02       26 阅读
  4. 学习 Rust十天:模式匹配

    2024-01-30 17:08:02       33 阅读
  5. 06-模式匹配

    2024-01-30 17:08:02       50 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-01-30 17:08:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-01-30 17:08:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-01-30 17:08:02       82 阅读
  4. Python语言-面向对象

    2024-01-30 17:08:02       91 阅读

热门阅读

  1. Tensorflow2.x实现用于model.fit()中的医学图像dataset

    2024-01-30 17:08:02       41 阅读
  2. js读取json的固定数据的一种方法

    2024-01-30 17:08:02       56 阅读
  3. html表单添加默认创建时间

    2024-01-30 17:08:02       57 阅读
  4. vue数据绑定

    2024-01-30 17:08:02       56 阅读
  5. 基础算法-差分-一维数组

    2024-01-30 17:08:02       51 阅读
  6. 基于STM32F103的路灯监控系统设计

    2024-01-30 17:08:02       45 阅读
  7. 聊聊PowerJob的SystemInfoController

    2024-01-30 17:08:02       46 阅读
  8. 小程序的应用、页面、组件生命周期(超全版)

    2024-01-30 17:08:02       57 阅读
  9. 获取文件夹下所有文件路径

    2024-01-30 17:08:02       56 阅读
  10. 代码随想录算法训练营|day21

    2024-01-30 17:08:02       66 阅读
  11. 提高 Code Review 质量的最佳实践

    2024-01-30 17:08:02       50 阅读