【Rust】Rust学习 第六章枚举和模式匹配

本章介绍 枚举enumerations ),也被称作 enums 。枚举允许你通过列举可能的 成员variants ) 来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。接下来,我们会探索一个特别有用的枚举,叫做**Option,它代表一个值要么是某个值要么什么都不是。** 然后会讲到在 match 表达式中用模式匹配,针对不同的枚举值编写相应要执行的代码。最后会介绍 if let,另一个简洁方便处理代码中枚举的结构。

6.1 定义枚举

定义一个枚举

rust 复制代码
fn main() {

}

enum IpAddrKind {
    V4,
    V6,
}

现在 IpAddrKind 就是一个可以在代码中使用的自定义数据类型了。

枚举值

可以像这样创建 IpAddrKind 两个不同成员的实例:

rust 复制代码
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

注意枚举的成员位于其标识符的命名空间中,并使用两个冒号分开。这么设计的益处是现在 IpAddrKind::V4IpAddrKind::V6 都是 IpAddrKind 类型的。

一个案例:

rust 复制代码
fn main() {
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"),
};
}

这里定义了一个有两个字段的结构体 IpAddrIpAddrKind(之前定义的枚举)类型的 kind 字段和 String 类型 address 字段。有这个结构体的两个实例。第一个,home,它的 kind 的值是 IpAddrKind::V4 与之相关联的地址数据是 127.0.0.1。第二个实例,loopbackkind 的值是 IpAddrKind 的另一个成员,V6,关联的地址是 ::1。使用了一个结构体将 kindaddress 打包在一起,现在枚举成员就与值相关联了。

可以使用一种更简洁的方式来表达相同的概念,仅仅使用枚举并将数据直接放进每一个枚举成员而不是将枚举作为结构体的一部分。

rust 复制代码
fn main() {
enum IpAddr {
    V4(String),
    V6(String),
}

let home = IpAddr::V4(String::from("127.0.0.1"));

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

用枚举替代结构体还有另一个优势:每个成员可以处理不同类型和数量的数据。IPv4 版本的 IP 地址总是含有四个值在 0 和 255 之间的数字部分。如果我们想要将 V4 地址存储为四个 u8 值而 V6 地址仍然表现为一个 String,这就不能使用结构体了。

rust 复制代码
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"));
}

结构体和枚举还有另一个相似点:就像可以使用 impl 来为结构体定义方法那样,也可以在枚举上定义方法。

rust 复制代码
fn main() {
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();
}

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

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

Option 是标准库定义的另一个枚举。Option 类型应用广泛因为它编码了一个非常普遍的场景,即一个值要么有值要么没值。从类型系统的角度来表达这个概念就意味着编译器需要检查是否处理了所有应该处理的情况,这样就可以避免在其他编程语言中非常常见的 bug。

Rust 并没有很多其他语言中有的空值功能。空值(Null )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。

Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是 Option<T>,而且它定义于标准库中,如下:

rust 复制代码
fn main() {
enum Option<T> {
    Some(T),
    None,
}
}

**Option<T> 枚举是如此有用以至于它甚至被包含在了 prelude 之中,你不需要将其显式引入作用域。另外,它的成员也是如此,可以不需要 Option:: 前缀来直接使用 SomeNone。**即便如此 Option<T> 也仍是常规的枚举,Some(T)None 仍是 Option<T> 的成员。

rust 复制代码
fn main() {
    let some_number = Some(5);
    let some_string = Some("a string");
    let absent_number:Option<i32> = None;

    println!("{:#?}, {:#?}, {:#?} ", some_number, some_string, absent_number);
}

结果

如果使用 None 而不是 Some,需要告诉 Rust Option<T> 是什么类型的,因为编译器只通过 None 值无法推断出 Some 成员保存的值的类型。

简而言之,因为 Option<T>T(这里 T 可以是任何类型)是不同的类型,编译器不允许像一个肯定有效的值那样使用 Option<T>

rust 复制代码
fn main() {
    let x : i32 = 8;
    let y:Option<i32> = Some(10);
    let sum = x + y;
}

结果

错误信息意味着 Rust 不知道该如何将 Option<i8>i8 相加,因为它们的类型不同。

6.2 match控制流运算符

match 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。

一个案例:

rust 复制代码
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

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

fn main() {
    let data = Coin::Nickel;
    println!("{}", value_in_cents(data));
    let data = Coin::Penny;
    println!("{}", value_in_cents(data));
    let data = Coin::Dime;
    println!("{}", value_in_cents(data));
    let data = Coin::Quarter;
    println!("{}", value_in_cents(data));
}

结果

拆开 value_in_cents 函数中的 match 来看。首先,我们列出 match 关键字后跟一个表达式,在这个例子中是 coin 的值。这看起来非常像 if 使用的表达式,不过这里有一个非常大的区别:对于 if,表达式必须返回一个布尔值,而这里它可以是任何类型的。

接下来是 match 的分支。一个分支有两个部分:一个模式和一些代码。第一个分支的模式是值 Coin::Penny 而之后的 => 运算符将模式和将要运行的代码分开。这里的代码就仅仅是值 1。每一个分支之间使用逗号分隔。

match 表达式执行时,它将结果值按顺序与每一个分支的模式相比较。如果模式匹配了这个值,这个模式相关联的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支,非常类似一个硬币分类器。

每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 match 表达式的返回值。

绑定值的模式

匹配分支的另一个有用的功能是可以绑定匹配的模式的部分值。这也就是如何从枚举成员中提取值的。

一个案例:

rust 复制代码
#[derive(Debug)]

enum UsState {
    Alabama,
    Alaska,
}

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 quater from {:?}!", state);
            25
        },
    }
}

fn main() {
    let data = Coin::Quarter(UsState::Alaska);

    println!("{}", value_in_cents(data));
}

结果

如果调用 value_in_cents(Coin::Quarter(UsState::Alaska))coin 将是 Coin::Quarter(UsState::Alaska)。当将值与每个分支相比较时,没有分支会匹配,直到遇到 Coin::Quarter(state)。这时,state 绑定的将会是值 UsState::Alaska。接着就可以在 println! 表达式中使用这个绑定了,像这样就可以获取 Coin 枚举的 Quarter 成员中内部的州的值。

匹配Option<T>

编写一个函数,它获取一个 Option<i32> 并且如果其中有一个值,将其加一。如果其中没有值,函数应该返回 None 值并不尝试执行任何操作。

rust 复制代码
fn main() {
    let five = Some(5);
    let five = plus_one(five);
    let six = plus_one(five);
    let none = plus_one(None);

    println!("{:?}, {:?}, {:?}", five, six, none);
}

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

结果

匹配上了就返回,匹配不上就会陷入死循环,rust会报错。

_通配符

Rust 也提供了一个模式用于不想列举出所有可能值的场景。例如,u8 可以拥有 0 到 255 的有效的值,如果我们只关心 1、3、5 和 7 这几个值,就并不想必须列出 0、2、4、6、8、9 一直到 255 的值。所幸不必这么做:可以使用特殊的模式 _ 替代:

rust 复制代码
fn main() {
let some_u8_value = 0u8;
match some_u8_value {
    1 => println!("one"),
    3 => println!("three"),
    5 => println!("five"),
    7 => println!("seven"),
    _ => println!("emo")
}
}

6.3 if let简洁控制流

if let 语法让我们以一种不那么冗长的方式结合 iflet,来处理只匹配一个模式的值而忽略其他模式的情况。

rust 复制代码
fn main() {
    let some_u8_value = Some(0u8);
    match some_u8_value {
        Some(3) => println!("three"),
        _ =>(),
    }
}

我们想要对 Some(3) 匹配进行操作但是不想处理任何其他 Some<u8> 值或 None 值。

可以使用 if let 这种更短的方式编写

rust 复制代码
fn main() {
    let some_u8_value = Some(0u8);
    if let Some(3) = some_u8_value {
        println!("three");
    }
}

if let 获取通过等号分隔的一个模式和一个表达式。它的工作方式与 match 相同,这里的表达式对应 match 而模式则对应第一个分支。

可以认为 if letmatch 的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值。

可以在 if let 中包含一个 elseelse 块中的代码与 match 表达式中的 _ 分支块中的代码相同,这样的 match 表达式就等同于 if letelse

参考:枚举与模式匹配 - Rust 程序设计语言 简体中文版 (bootcss.com)

相关推荐
無限進步D3 小时前
Java 运行原理
java·开发语言·入门
是苏浙3 小时前
JDK17新增特性
java·开发语言
不光头强4 小时前
spring cloud知识总结
后端·spring·spring cloud
GetcharZp7 小时前
告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
后端
阿里加多7 小时前
第 4 章:Go 线程模型——GMP 深度解析
java·开发语言·后端·golang
likerhood7 小时前
java中`==`和`.equals()`区别
java·开发语言·python
小小李程序员7 小时前
Langchain4j工具调用获取不到ThreadLocal
java·后端·ai
zs宝来了8 小时前
AQS详解
java·开发语言·jvm