【从零开始学习Rust】枚举和模式匹配

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,它有两个变体:v4v6,用于表示 IP 地址的类型IPv4IPv6
  • 定义一个结构体 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,它有两个变体:CircleSquare,分别表示圆形和正方形。然后,我们为 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 类型通常用于处理可能出现空值的情况,例如当你尝试从一个容器(比如 VecHashMap)中获取元素时,如果元素不存在,容器会返回一个 None 值。

Option 类型的使用通常和模式匹配一起使用,以处理可能存在和不存在的情况。例如,你可以使用 match 表达式来处理 Option 类型的值,分别处理 SomeNone 两种情况。

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 进行模式匹配,分别处理 SomeNone 两种情况。

Option 类型的使用可以帮助你在编写 Rust 代码时更加安全地处理可能为空的情况,避免了空指针异常等问题。因此,Option 类型在 Rust 中被广泛应用于处理可能存在空值的场景。

0x4 枚举和结构体的区别

  1. 枚举(enums)
    • 枚举是一种类型,它的取值是有限的且是预定义的。枚举可以包含多个变体,每个变体可以包含不同类型的数据,也可以是不包含任何数据的单元类型。
    • 枚举通常用于表示某种类型的多个可能状态或取值,例如,表示不同类型的事件、状态或选项等。
    • 枚举的变体可以通过模式匹配来处理不同的情况,这使得枚举在 Rust 中非常灵活和强大。
  2. 结构体(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 枚举,包含了 TextNumber 两种变体。
  • main 函数中,我们创建了一个 Message::Number(42) 的实例,并使用 if let 表达式来匹配这个实例。
  • 如果 msgMessage::Number 类型的,打印出相应的消息;否则打印出 “Received a message of a different type”。

5.3 match和if let的区别

matchif let 都是 Rust 中用于模式匹配的工具,它们都可以用于处理不同的情况,并执行相应的代码逻辑。但是,它们在使用方式和适用场景上有一些不同之处。

匹配方式

  • match 表达式可以匹配多个模式,并根据匹配的情况执行不同的代码块。它可以处理多种情况下的模式匹配。
  • if let 表达式用于匹配单个模式,并且只有在匹配成功时执行相应的代码块。它通常用于处理特定情况下的模式匹配。

适用场景

  • 当你需要处理多个不同的模式匹配情况时,通常会使用 match 表达式,因为它可以覆盖更多的情况并提供更多的灵活性。
  • 当你只需要处理单个模式匹配情况时,可以使用 if let 表达式,因为它更加简洁和直观。
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shixfer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值