枚举
枚举类型,通常也被简称为枚举,它允许我们列举所有可能的值来定义一个类型,对于用过 TS 的前端同学来说会容易理解一些,不过 rust 中的枚举类型更强大。
定义枚举
定义一个 IP 地址的枚举类型,目前有两种被广泛使用的 IP 地址标准:IPv4 和 IPv6,两种 IP 类型被称作变体:
#[derive(Debug)] // 加了Debug trait才能打印每一个枚举值
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
println!("{:?}", four); // V4
println!("{:?}", six); // V6
复制代码
使用枚举
枚举值可以用来标识函数参数的类型:
// 标识参数类型
fn route(ip: IpAddrKind) {
println!("{:?}", ip)
}
route(IpAddrKind::V4); // V4
route(IpAddrKind::V6); // V6
复制代码
在结构体中使用
定义一个结构体,包含 IP 类型和 IP 地址:
struct IpAddr {
ip: IpAddrKind,
address: String,
}
// 实现一个IP类型为V4的结构体
let home = IpAddr {
ip: IpAddrKind::V4,
address: String::from("127.0.0.1")
};
// 实现一个IP类型为V6的结构体
let loopback = IpAddr {
ip: IpAddrKind::V6,
address: String::from("::1")
};
复制代码
枚举关联类型/值
事实上枚举允许我们直接将其关联的数据嵌入枚举变体内。我们可以使用枚举来更简捷地表达出上述概念,而不用将枚举集成至结构体中:
#[derive(Debug)]
enum IpAddrKind2 {
V4(String), // 关联一个String类型
V6(String), // 关联一个String类型
}
// 创建V4变体的枚举,并关联String
let home = IpAddrKind2::V4(String::from("127.0.0.1"));
// 创建V6变体的枚举,并关联String
let loopback = IpAddrKind2::V6(String::from("::1"));
println!("{:?}", home); // V4("127.0.0.1")
println!("{:?}", loopback); // V6("::1")
复制代码
枚举支持多参数
将 V4 用多个参数来更加详细的描述地址,其实可以将关联的参数整体理解成元组:
#[derive(Debug)]
enum IpAddrKind3 {
V4(u8, u8, u8, u8), // 将String改为4个u8
V6(String),
}
// 使用时也要传入4个u8类型的参数
let home = IpAddrKind3::V4(255, 255, 255, 255);
let loopback = IpAddrKind3::V6(String::from("::1"));
println!("{:?}", home); // V4(255, 255, 255, 255)
println!("{:?}", loopback); // V6("::1")
复制代码
多类型枚举与结构体
一个枚举中可以包含多个类型的变体:
#[derive(Debug)]
enum Message {
// 没有关联任何数据
Quit,
// 包含一个匿名结构体
Move { x: i32, y: i32 },
// 包含一个String
Write (String),
// 包含三个i32(一个元祖)
ChangeColor (u8, u8, u8),
}
// 创建一个Move变体
let mov = Message::Move { x: 100, y: 100 };
println!("{:?}", mov); // Move { x: 100, y: 100 }
复制代码
枚举和结构体的区别
其实上边的多类型枚举的例子中,每一种变体都可以用单独的结构体实现:
// 结构体
struct Quit; // 空结构体
struct Move { x: i32, y: i32 }
struct Write(String); // 元组结构体
struct ChangeColor(u8, u8, u8); // 元组结构体
复制代码
两种实现方式之间的差别在于,假如我们使用了不同的结构体,那么每个结构体都会拥有自己的类型,我们无法定义一个能够统一处理这些类型数据的函数,而枚举则不同,因为它是单独的一个类型,只要在函数中标注参数类型为枚举即可:
// 使用枚举来标识参数类型
fn poster(msg: Message) {}
// 使用结构体的话,需要四个不同的函数
fn quit_poster(msg: Quit) {}
fn move_poster(msg: Move) {}
fn write_poster(msg: Write) {}
fn change_color_poster(msg: ChangeColor) {}
复制代码
为枚举实现方法
枚举也可以像结构体一样实现方法:
impl Message {
fn call(&self) -> &Message {
return self;
}
}
let msg = Message::Move { x: 100, y: 100 };
msg.call(); // Move { x: 100, y: 100 }
复制代码
Option 枚举
rust 中没有空值,但却提供了一个拥有类似概念的枚举,我们可以用它来标识一个值无效或缺失。这个枚举就是 Option<T>:
enum Option<T> { // 这个枚举中包含一个泛型T,后边章节会介绍泛型
Some(T),
None,
}
复制代码
由于 Option 非常常用,rust 已经做了优化,我们可以在不加 Option::前缀的情况下直接使用 Some 或 None:
// rust会自动推断类型
let some_string = Option::Some("some");
// 也可以显式标注类型
let some_string: Option<&str> = Option::Some("some");
// 可以省略Option::
let some_string: Option<&str> = Some("some");
// None必须显式给出类型声明,编译器无法推断出类型
let absent_string: Option<i32> = Option::None;
// 省略Option::
let absent_string: Option<i32> = None;
println!("{:?}", some_string); // Some("some")
println!("{:?}", absent_string); // None
复制代码
使用 Option 类型时,其中包含的值不能直接使用:
let x: i32 = 10;
let o:Option<i32> = Some(5);
println!("{}", x + o) // 报错,i32类型不能与Option相加
复制代码
如果要进行运算,必须要将 Option<T>中的 T 取出来,例如使用 if let 表达式:
if let Some(v) = o {
println!("{}", x + v); // 15
}
复制代码
评论