0x0 概述
枚举(enums)是一种自定义数据类型,它允许开发者定义一个类型,该类型的取值是有限的且是预定义的。
0x1 定义枚举
use std::fmt;
#[derive(Debug)]
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
impl fmt::Display for IpAddr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ip type is {:?}, address is {}", self.kind, self.address)
}
}
fn main() {
let ip_addr_v4 = IpAddr { kind: (IpAddrKind::V4), address: "10.10.10".to_string() };
let ip_addr_v6 = IpAddr { kind: (IpAddrKind::V6), address: "10.10.10".to_string() };
println!(" {},\n {}", ip_addr_v4, ip_addr_v6);
}
- 定义了一个枚举
IpAddrKind
,它有两个变体:v4
和v6
,用于表示 IP 地址的类型IPv4
或IPv6
- 定义一个结构体
IpAddr
,用于表示 IP 地址本身。 - 为
IpAddr
结构体实现了Display
trait,使其能够通过{}
格式化输出符号进行格式化输出。
实际上存储和编码 IP 地址实在是太常见了以致标准库提供了一个开箱即用的定义!下面是标准库中提供的代码原型:
pub enum IpAddr {
/// An IPv4 address.
#[stable(feature = "ip_addr", since = "1.7.0")]
V4(#[stable(feature = "ip_addr", since = "1.7.0")] Ipv4Addr),
/// An IPv6 address.
#[stable(feature = "ip_addr", since = "1.7.0")]
V6(#[stable(feature = "ip_addr", since = "1.7.0")] Ipv6Addr),
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Ipv4Addr {
octets: [u8; 4],
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Ipv6Addr {
octets: [u8; 16],
}
#[stable(feature = "ip_addr", since = "1.7.0")]
impl fmt::Display for IpAddr {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IpAddr::V4(ip) => ip.fmt(fmt),
IpAddr::V6(ip) => ip.fmt(fmt),
}
}
}
0x2 为枚举定义方法
下面是一个简单的例子,我们将为一个枚举类型定义一个方法,以实现计算边长的功能:
enum Shape {
Circle(f64),
Square(f64),
}
impl Shape {
fn perimeter(&self) -> f64 {
match self {
Shape::Circle(diameter) => std::f64::consts::PI * diameter,
Shape::Square(length) => length * length,
}
}
}
fn main() {
let circle = Shape::Circle(3.0);
let square = Shape::Square(4.0);
println!("Circle perimeter: {}", circle.perimeter());
println!("Square perimeter: {}", square.perimeter());
}
-
我们定义了一个枚举
Shape
,它有两个变体:Circle
和Square
,分别表示圆形和正方形。然后,我们为Shape
枚举实现了一个方法perimeter
,用来计算不同形状的周长。在perimeter
方法中,我们使用match
表达式根据枚举的不同变体计算周长并返回结果。 -
在
main
函数中,我们创建了一个圆形和一个正方形,并分别调用它们的perimeter
方法来计算周长并打印结果。
通过这种方式,我们为枚举类型添加了一个行为,使其具备了计算周长的能力。
0x3 特殊的Option
在 Rust 中,Option
是一个枚举类型,它代表一个值可能存在,也可能不存在的情况。Option
类型在标准库中定义如下:
enum Option<T> {
Some(T),
None,
}
Option
是一个泛型枚举类型,它可以包含任意类型T
的值。Option
有两个变体:Some(T)
:表示值存在,同时包含了一个类型为T
的值。None
:表示值不存在,不包含任何值。
Option
类型通常用于处理可能出现空值的情况,例如当你尝试从一个容器(比如 Vec
或 HashMap
)中获取元素时,如果元素不存在,容器会返回一个 None
值。
Option
类型的使用通常和模式匹配一起使用,以处理可能存在和不存在的情况。例如,你可以使用 match
表达式来处理 Option
类型的值,分别处理 Some
和 None
两种情况。
fn divide(x: f64, y: f64) -> Option<f64> {
if y == 0.0 {
None
} else {
Some(x / y)
}
}
fn main() {
let result = divide(10.0, 0.0);
match result {
Some(value) => println!("Result: {}", value),
None => println!("Cannot divide by zero!"),
}
}
输出结果:
在上面的例子中,divide
函数返回一个 Option<f64>
类型的值,表示除法的结果可能存在,也可能不存在。在 main
函数中,使用 match
表达式对 result
进行模式匹配,分别处理 Some
和 None
两种情况。
Option
类型的使用可以帮助你在编写 Rust 代码时更加安全地处理可能为空的情况,避免了空指针异常等问题。因此,Option
类型在 Rust 中被广泛应用于处理可能存在空值的场景。
0x4 枚举和结构体的区别
- 枚举(enums):
- 枚举是一种类型,它的取值是有限的且是预定义的。枚举可以包含多个变体,每个变体可以包含不同类型的数据,也可以是不包含任何数据的单元类型。
- 枚举通常用于表示某种类型的多个可能状态或取值,例如,表示不同类型的事件、状态或选项等。
- 枚举的变体可以通过模式匹配来处理不同的情况,这使得枚举在 Rust 中非常灵活和强大。
- 结构体(structs):
- 结构体是一种类型,它可以包含多个命名字段,每个字段可以是不同类型的数据。
- 结构体通常用于表示一种具体的数据结构,例如,表示一个人的信息、一个图形的属性等。
- 结构体的字段可以通过点运算符来访问和修改,它们提供了一种组织和存储数据的方式。
枚举和结构体在 Rust 中都是自定义的数据类型,它们分别适用于不同的场景和需求。枚举适用于表示有限的预定义取值,而结构体适用于表示具体的数据结构。
0x5 模式匹配
在Rust中,模式匹配是一种非常强大和灵活的语言特性,它允许你根据数据的结构和值来进行条件匹配和分支处理。Rust中的模式匹配有多种形式,其中最常见的是使用 match 表达式和 if let 表达式。
5.1 match 表达式
match
表达式是一种强大的条件匹配和分支处理工具。它允许你根据给定的值匹配不同的模式,并执行相应的代码块。match
表达式的基本语法如下:
match value {
pattern1 => {
// 对应 pattern1 的处理逻辑
}
pattern2 => {
// 对应 pattern2 的处理逻辑
}
// 更多模式和处理逻辑
_ => {
// 默认处理逻辑,类似于 switch 语句中的 default
}
}
在 match
表达式中,每个模式都可以是常量、变量、通配符 _
、范围、结构体、枚举、引用等,你可以根据具体情况进行匹配。match
表达式要求覆盖所有可能的情况,因此通常会使用通配符 _
来处理未覆盖的情况。
fn divide(x: f64, y: f64) -> Option<f64> {
if y == 0.0 {
None
} else {
Some(x / y)
}
}
fn main() {
let result = divide(10.0, 5.0);
match result {
Some(value) => println!("Result: {}", value),
None => println!("Cannot divide by zero!"),
}
}
通过Option
返回值来进行匹配,除法运算中0
是不能做为除数的,所以当y
为0时返回None
。
当如果我们没有在match
块中实现None
时,编译时会失败,并提示我们需要添加None
分支来确保所有可能的情况都得到处理。
5.2 if let 表达式
if let
表达式是一种简化版的模式匹配语法,用于匹配单个模式并执行相应的代码块。它通常用于匹配枚举变体或某种特定的模式。if let
表达式的语法如下:
if let pattern = value {
// 对应 pattern 的处理逻辑
} else {
// 其他情况的处理逻辑
}
if let
表达式会尝试将给定的值与指定的模式进行匹配,如果匹配成功,则执行对应的代码块;如果匹配失败,则执行 else
分支的代码块。
enum Message {
Text(String),
Number(i32),
}
fn main() {
let msg = Message::Number(42);
// 使用 if let 匹配枚举的 Number 变体
if let Message::Number(num) = msg {
println!("Received a number message: {}", num);
} else {
println!("Received a message of a different type");
}
}
- 我们定义了一个
Message
枚举,包含了Text
和Number
两种变体。 - 在
main
函数中,我们创建了一个Message::Number(42)
的实例,并使用if let
表达式来匹配这个实例。 - 如果
msg
是Message::Number
类型的,打印出相应的消息;否则打印出 “Received a message of a different type”。
5.3 match和if let的区别
match
和 if let
都是 Rust 中用于模式匹配的工具,它们都可以用于处理不同的情况,并执行相应的代码逻辑。但是,它们在使用方式和适用场景上有一些不同之处。
匹配方式
match
表达式可以匹配多个模式,并根据匹配的情况执行不同的代码块。它可以处理多种情况下的模式匹配。if let
表达式用于匹配单个模式,并且只有在匹配成功时执行相应的代码块。它通常用于处理特定情况下的模式匹配。
适用场景
- 当你需要处理多个不同的模式匹配情况时,通常会使用
match
表达式,因为它可以覆盖更多的情况并提供更多的灵活性。 - 当你只需要处理单个模式匹配情况时,可以使用
if let
表达式,因为它更加简洁和直观。