基础篇-枚举类型与匹配

  • 木头
  • 更新于 2023-02-24 13:17
  • 阅读 1297

枚举定义,枚举方法,match匹配

枚举(enumerations),也被称作 enums。枚举允许你通过列举可能的成员(variants)来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。然后会讲到在match 表达式中用模式匹配,针对不同的枚举值编写相应要执行的代码。

枚举的定义

假设我们要处理 IP 地址。目前被广泛使用的两个主要 IP 标准:IPv4(version four)IPv6(version six)。这是我们的程序可能会遇到的所有可能的 IP 地址类型:所以可以 枚举 出所有可能的值,这也正是此枚举名字的由来。

任何一个 IP 地址要么是 IPv4 的要么是 IPv6 的,而且不能两者都是。IP 地址的这个特性使得枚举数据结构非常适合这个场景,因为枚举值只可能是其中一个成员。IPv4IPv6从根本上讲仍是 IP 地址,所以当代码在处理适用于任何类型的 IP地址的场景时应该把它们当作相同的类型。

可以通过在代码中定义一个 IpAddrKind 枚举来表现这个概念并列出可能的 IP地址类型,V4V6。这被称为枚举的 成员(variants)

enum IpAddrKind {
    V4,
    V6,
}

枚举值

进一步考虑一下我们的IP 地址类型,目前没有一个存储实际IP 地址 数据 的方法;只知道它是什么 类型 的。考虑到已经学习过结构体了,看示例:

// ip地址类型枚举
enum IpAddrKind {
    V4,
    V6,
}
// ip地址结构体
struct IpAddr {
    kind: IpAddrKind,
    address: String,
}

fn main() {
    let i1 = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),
    };
    let i2 = IpAddr {
        kind: IpAddrKind::V6,
        address: String::from("::1"),
    };
}

我们创建这个结构体的两个实例。第一个,i1,它的 kind 的值是 IpAddrKind::V4 与之相关联的地址数据是 127.0.0.1。第二个实例,i2kind 的值是 IpAddrKind 的另一个成员,V6,关联的地址是 ::1。我们使用了一个结构体来将kindaddress 打包在一起,现在枚举成员就与值相关联了。

我们可以使用一种更简洁的方式来表达相同的概念,仅仅使用枚举并将数据直接放进每一个枚举成员而不是将枚举作为结构体的一部分。IpAddr 枚举的新定义表明了 V4V6成员都关联了 String 值:

enum IpAddr {
    V4(String),
    V6(String),
}
fn main() {
    let i1 = IpAddr::V4(String::from("127.0.0.1"));
    let i2 = IpAddr::V6(String::from("::1"));
}

我们直接将数据附加到枚举的每个成员上,这样就不需要一个额外的结构体了。这里也很容易看出枚举工作的另一个细节:每一个我们定义的枚举成员的名字也变成了一个构建枚举的实例的函数。也就是说,IpAddr::V4() 是一个获取String参数并返回 IpAddr 类型实例的函数调用。作为定义枚举的结果,这些构造函数会自动被定义。

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

enum IpAddr {
    V4(u8, u8, u8, u8),
    V6(String),
}
fn main() {
    let i1 = IpAddr::V4(127, 0, 0, 1);
    let i2 = IpAddr::V6(String::from("::1"));
}

枚举成员中内嵌了多种多样的类型:

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

这个枚举有四个含有不同类型的成员:

  • Quit 没有关联任何数据。
  • Move 类似结构体包含命名字段。
  • Write 包含单独一个 String。
  • ChangeColor 包含三个 i32。

枚举方法

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

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

impl Message {
    fn call(&self) {}
}
fn main() {
    // 调用方法一
    let m = Message::Write(String::from("hello"));
    m.call();

    // 调用方法二
    Message::Write(String::from("hello")).call();
}

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

match 匹配

Rust 有一个叫做 match 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成,我们使用match打印Message枚举:

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

impl Message {
    fn prin(&self) {
        match self {
            Message::Quit => println!("Quit"),
            Message::Move { x, y } => println!("Move x = {}, y = {}", x, y),
            Message::ChangeColor(a, b, c) => {
                println!("ChangeColor a = {}, b = {}, c = {}", a, b, c)
            }
            _ => println!("Write"),
        }
    }
}
fn main() {
    Message::Quit.prin();

    Message::Move { x: 10, y: 12 }.prin();

    Message::Write(String::from("write")).prin();

    Message::ChangeColor(1, 2, 3).prin();
}

match 表达式执行时,它将结果值按顺序与每一个分支的模式相比较。如果模式匹配了这个值,这个模式相关联的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支。match所罗列的匹配,必须穷举出其所有可能。当然,你也可以用_这个符号来代表其余的所有可能性情况。

  • 原创
  • 学分: 3
  • 分类: Rust
  • 标签:
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
121 订阅 31 篇文章

0 条评论

请先 登录 后评论
木头
木头
0xC020...10cf
江湖只有他的大名,没有他的介绍。