Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

6.1 定义枚举

6.1.1. 什么是枚举?

枚举允许我们通过列举所有可能的值来定义一个类型。这与其他编程语言中的枚举类似,但 Rust 的枚举更加灵活和强大,因为它们可以关联数据和方法,类似于其他语言中的类或结构体。

6.1.2. 定义枚举

举个例子,IP地址只有两种可能——IPv4和IPv6。它要么是IPv4,要么是IPv6,所以非常适合用枚举,因为枚举的值只能是其所有变体(枚举所有可能的值)中的一个。

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

这段代码使用enum关键字声明了一个名为IpAddrKind的枚举类型,它有两个变体——V4V6——分别代表IPv4和IPv6。

6.1.3. 枚举值

创建枚举值非常简单,格式为枚举名::变体。例如:

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

枚举的变体位于该枚举标识符的命名空间下,而这个标识符就是枚举类型的名字。

我们可以声明一个接收IpAddrKind作为参数的函数,传入的值既可以是V4也可以是V6

#![allow(unused)]
fn main() {
fn route(ip_addr: IpAddrKind) {
    match ip_addr {
        IpAddrKind::V4 => println!("IPv4"),
        IpAddrKind::V6 => println!("IPv6"),
    }
}
}

让我们试试效果: 完整代码:

enum IpAddrKind{
    V4,
    V6,
}

fn main() {
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;
    // 调用函数
    route(four);
    route(six);
    route(IpAddrKind::V4);
}

fn route(ip_addr: IpAddrKind) {
    match ip_addr {
        IpAddrKind::V4 => println!("IPv4"),
        IpAddrKind::V6 => println!("IPv6"),
    }
}

输出:

IPv4
IPv6
IPv4

6.1.4. 将数据附加到枚举变体中

枚举是一种自定义数据类型,所以可以用作结构体字段的类型,例如:

#![allow(unused)]
fn main() {
struct IpAddr {
    kind: IpAddrKind,
    address: String,
}
}

IpAddr中的kind字段类型是IpAddrKind,用于存储网络协议;另一个字段addressString类型,用于存储具体的IP地址。

通过这样的结构体,我们可以在main()函数中声明一些存储IPv4、IPv6信息的变量:

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

Rust允许把数据直接附加到枚举变体上,例如:

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

在每个变体后面加上一个类型(不必是同一种类型)。这里V4V6后面都跟了String类型。

这种做法的优点是:

  • 不需要额外使用结构体
  • 每个变体可以有不同的类型,以及不同数量的关联数据

例如:

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

IPv4实际上由四个8位数字组成(也就是四个能装进u8的值),而IPv6是字符串,所以应使用String。如果我们想把V4地址存成四个u8值,同时仍把V6地址表示成String,就无法使用结构体。枚举可以轻松处理这种情况。

我们来重写一下前面的代码:

enum IpAddrKind{
    V4(u8, u8, u8, u8),
    V6(String),
}

fn main() {
    let home = IpAddrKind::V4(127, 0, 0, 1);
    let loopback = IpAddrKind::V6(String::from("::1"));
}

确实比前面的代码短多了。

6.1.5. 标准库中的IpAddr

事实上,标准库已经提供了表示IP地址的枚举。来看一下官方是怎么写的:

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

struct Ipv6Addr {
    // --snip--
}

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

Ipv4AddrIpv6Addr的内容这里没有写出来,但这不是重点。重点是此代码说明任何类型的数据都可以放进枚举变体中:例如字符串、数字类型或结构体。甚至还可以包含另一个枚举。

6.1.6. 在枚举上使用方法(Method)

方法(Method)的概念在上一篇文章 5.3. struct的方法(Method) 中已经介绍过,这里不再过多展开。定义方法使用impl关键字,如下例:

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}
impl Message {
    fn call(&self) {
        println!("Something happens");
    }
}
fn main(){
	let m = Message::Write(String::from("hello"));
    m.call();
}

该枚举有四种不同的变体:

  • Quit:不携带任何数据。
  • Move:包含一个匿名结构体。
  • Write:包含一个String
  • ChangeColor:包含三个i32值。

main中,变量m被声明为Message枚举的Write变体,并附带了Stringhello。然后在m上调用call方法,就会打印Something happens