6.1 定义枚举
6.1.1. 什么是枚举?
枚举允许我们通过列举所有可能的值来定义一个类型。这与其他编程语言中的枚举类似,但 Rust 的枚举更加灵活和强大,因为它们可以关联数据和方法,类似于其他语言中的类或结构体。
6.1.2. 定义枚举
举个例子,IP地址只有两种可能——IPv4和IPv6。它要么是IPv4,要么是IPv6,所以非常适合用枚举,因为枚举的值只能是其所有变体(枚举所有可能的值)中的一个。
#![allow(unused)]
fn main() {
enum IpAddrKind{
V4,
V6,
}
}
这段代码使用enum关键字声明了一个名为IpAddrKind的枚举类型,它有两个变体——V4和V6——分别代表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,用于存储网络协议;另一个字段address是String类型,用于存储具体的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),
}
}
在每个变体后面加上一个类型(不必是同一种类型)。这里V4和V6后面都跟了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),
}
}
Ipv4Addr和Ipv6Addr的内容这里没有写出来,但这不是重点。重点是此代码说明任何类型的数据都可以放进枚举变体中:例如字符串、数字类型或结构体。甚至还可以包含另一个枚举。
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变体,并附带了String值hello。然后在m上调用call方法,就会打印Something happens。