014、枚举与模式匹配

news2024/11/28 8:43:38

        枚举类型,通常也被简称为枚举,它允许我们列举所有可能的值来定义一个类型。在本篇文章中,我们首先会定义并使用一个枚举,以向你展示枚举是如何连同数据来一起编码信息的。

        接着,我们会讨论一个特别有用的枚举:Option,它常常被用来描述某些可能不存在的值。随后,我们将学会如何在 match 表达式中使用模式匹配,并根据不同的枚举值来执行不同的代码。

        最后,我们还会介绍另外一种常用的结构 if let,它可以在某些场景下简化我们处理枚举的代码。你可以找到许多拥有枚举特性的语言,但它们提供的具体功能却不尽相同。

        如果一定要比较的话,Rust中的枚举更类似于F#、OCaml和Haskell这类函数式编程语言中的代数数据类型(algebraic data type)。

1.定义枚举

        现在,让我们来尝试处理一个实际的编码问题,并接着讨论在这种情形下,为什么使用枚举要比结构体更加合适。

        假设我们需要对IP地址进行处理,那么目前有两种被广泛使用的IP地址标准:IPv4和IPv6。因为我们只需要处理这两种情形,所以可以将所有可能的值枚举出来,这也正是枚举名字的由来。

        另外,一个IP地址要么是IPv4的,要么是IPv6的,没有办法同时满足两种标准。这个特性使得IP地址非常适合使用枚举结构来进行描述,因为枚举的值也只能是变体中的一个成员。

        无论是IPv4还是IPv6,它们都属于基础的IP地址协议,所以当我们需要在代码中处理IP地址时,应该将它们视作同一种类型。我们可以通过定义枚举IpAddrKind来表达这样的概念,声明该枚举需要列举出所有可能的IP地址种类—V4和V6,这也就是所谓的枚举变体(variant):

enum IpAddrKind {
    V4,
    V6,
}

        现在,IpAddrKind 就是一个可以在代码中随处使用的自定义数据类型了。 

2. 枚举值

        我们可以像下面的代码一样分别使用 IpAddrKind 中的两个变体来创建实例: 

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

        需要注意的是,枚举的变体全都位于其标识符的命名空间中,并使用两个冒号来将标识符和变体分隔开来。由于 IpAddrKind::V4 IpAddrKind::V6 拥有相同的类型 IpAddrKind,所以我们可以定义一个接收 IpAddrKind 类型参数的函数来统一处理它们: 

fn route(ip_type: IpAddrKind) { }

        现在,我们可以使用任意一个变体来调用这个函数了:

route(IpAddrKind::V4);
route(IpAddrKind::V6);

        除此之外,使用枚举还有很多优势。让我们继续考察这个IP地址类型,到目前为止,我们只能知道IP地址的种类,却还没有办法去存储实际的IP地址数据。考虑到我们刚刚学习了结构体,所以你也许会像示例6-1所示的那样去解决这个问题。 

// 示例6-1:使用struct来存储IP地址的数据和IpAddrKind变体

❶enum IpAddrKind {
    V4,
    V6,
}

❷struct IpAddr {
 ❸ kind: IpAddrKind,
 ❹ address: String,
}

❺let home = IpAddr {
    kind: IpAddrKind::V4,
    address: String::from("127.0.0.1"),
};

❻let loopback = IpAddr {
    kind: IpAddrKind::V6,
    address: String::from("::1"),
};

        上面的代码定义了拥有两个字段的结构体IpAddr❷:一个IpAddrKind类型(也就是我们之前定义的枚举❶)的字段kind❸,以及一个String类型的字段address❹。另外,我们还分别创建了两个不同的结构体实例。

        第一个实例,home❺,使用了IpAddrKind::V4作为字段kind的值,并存储了关联的地址数据127.0.0.1。第二个实例,loopback❻,存储了IpAddrKind的另外一个变体V6作为kind的值,并存储了关联的地址::1

        新结构体组合了kindaddress的值,现在,变体就和具体数据关联起来了。实际上,枚举允许我们直接将其关联的数据嵌入枚举变体内。我们可以使用枚举来更简捷地表达出上述概念,而不用将枚举集成至结构体中。

        在新的IpAddr枚举定义中,V4V6两个变体都被关联上了一个String值: 

enum IpAddr {
    V4(String),
    V6(String),
}

let home = IpAddr::V4(String::from("127.0.0.1"));

let loopback = IpAddr::V6(String::from("::1"));

        我们直接将数据附加到了枚举的每个变体中,这样便不需要额外地使用结构体。另外一个使用枚举代替结构体的优势在于:每个变体可以拥有不同类型和数量的关联数据。

        还是以IP地址为例,IPv4地址总是由40~255之间的整数部分组成。假如我们希望使用4u8值来代表V4地址,并依然使用String值来代表V6地址,那么结构体就无法轻易实现这一目的了,而枚举则可以轻松地处理此类情形: 

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

let home = IpAddr::V4(127, 0, 0, 1);

let loopback = IpAddr::V6(String::from("::1"));

        目前,我们已经为存储IPv4地址及IPv6地址的数据结构给出了好几种不同的方案。但实际上,由于存储和编码IP地址的工作实在太常见了,因此标准库为我们内置了一套可以开箱即用的定义!

        让我们来看一看标准库是如何设计IpAddr的。它采用了和我们自定义一样的枚举和变体定义,但将两个变体中的地址数据各自组装到了两个独立的结构体中: 

struct Ipv4Addr {
    // --略--
}

struct Ipv6Addr {
    // --略--
}

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

        在这段代码中,你可以在枚举的变体中嵌入任意类型的数据,无论是字符串、数值,还是结构体,甚至可以嵌入另外一个枚举!

        另外,标准库中的类型通常不会比我们设想的实现要复杂多少。需要注意的是,虽然标准库中包含了一份IpAddr的定义,但由于我们没有把它引入当前的作用域,所以可以无冲突地继续创建和使用自己定义的版本。

        我们会在后面深入讨论作用域引入。继续来看示例6-2中另外一个关于枚举的例子,它的变体中内嵌了各式各样的数据类型。 

// 示例6-2:枚举Message的变体拥有不同数量和类型的内嵌数据

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

        这个枚举拥有4个内嵌了不同类型数据的变体:

Quit没有任何关联数据。

Move包含了一个匿名结构体。

Write包含了一个String

ChangeColor包含了3i32值。

        定义示例6-2中的枚举有些类似于定义多个不同类型的结构体。但枚举除了不会使用struct关键字,还将变体们组合到了同一个Message类型中。下面代码中的结构体可以存储与这些变体完全一样的数据: 

        两种实现方式之间的差别在于,假如我们使用了不同的结构体,那么每个结构体都会拥有自己的类型,我们无法轻易定义一个能够统一处理这些类型数据的函数,而我们定义在示例6-2中的Message枚举则不同,因为它是单独的一个类型。

        枚举和结构体还有一点相似的地方在于:正如我们可以使用impl关键字定义结构体的方法一样,我们同样可以定义枚举的方法。下面的代码在Message枚举中实现了一个名为call的方法: 

impl Message {
    fn call(&self) {
     ❶ // 方法体可以在这里定义
    }
}

❷let m = Message::Write(String::from("hello"));
m.call();

        方法定义中的代码同样可以使用self来获得调用此方法的实例。在这个例子中,我们创建了一个变量 m❷,并为其赋予了值Message::Write(String::from("hello")),而该值也就是执行m.call()指令时传入call方法❶的self

        让我们再来看一看标准库中提供的另外一个非常常见且实用的枚举:Option

3. Option枚举及其在空值处理方面的优势

        在前文中,我们看到了IpAddr枚举是如何利用Rust的类型系统来将更多的信息,而不仅仅是数据,编码到程序中去的。而本节则会针对性地研究一个定义于标准库中的枚举:Option

        由于这里的Option类型描述了一种值可能不存在的情形,所以它被非常广泛地应用在各种地方。将这一概念使用类型系统描述出来意味着,编译器可以自动检查我们是否妥善地处理了所有应该被处理的情况。

        使用这一功能可以避免某些在其他语言中极其常见的错误。在设计编程语言时往往会规划出各式各样的功能,但思考应当避免设计哪些功能也是一门非常重要的功课。Rust并没有像许多其他语言一样支持空值。空值(Null)本身是一个值,但它的含义却是没有值。

        在设计有空值的语言中,一个变量往往处于这两种状态:空值或非空值。Tony Hoare,空值的发明者,曾经在2009年的一次演讲Null References: The Billion Dollar Mistake中提到: 

这是一个价值数十亿美金的错误设计。当时,我正在为一门面向对象语言中的引用设计一套全面的类型系统。我的目标是,通过编译器自动检查来确保所有关于引用的操作都是百分之百安全的。但是我却没有抵挡住引入一个空引用概念的诱惑,仅仅是因为这样会比较容易去实现这套系统。这导致了无数的错误、漏洞和系统崩溃,并在之后的40多年中造成了价值数10亿美金的损失。

        空值的问题在于,当你尝试像使用非空值那样使用空值时,就会触发某种程度上的错误。因为空或非空的属性被广泛散布在程序中,所以你很难避免引起类似的问题。

        但是不管怎么说,空值本身所尝试表达的概念仍然是有意义的:它代表了因为某种原因而变为无效或缺失的值。引发这些问题的关键并不是概念本身,而是那些具体的实现措施。

        因此,Rust中虽然没有空值,但却提供了一个拥有类似概念的枚举,我们可以用它来标识一个值无效或缺失。这个枚举就是Option<T>,它在标准库中被定义为如下所示的样子: 

enum Option<T> {
    Some(T),
    None,
}

        由于Option<T>枚举非常常见且很有用,所以它也被包含在了预导入模块中,这意味着我们不需要显式地将它引入作用域。

        另外,它的变体也是这样的:我们可以在不加Option::前缀的情况下直接使用SomeNone。但Option<T>枚举依然只是一个普通的枚举类型,Some(T)None也依然只是Option<T>类型的变体。

        这里的语法<T>是一个我们还没有学到的Rust功能。它是一个泛型参数,我们将会在后文讨论关于泛型的更多细节。现在,你只需要知道<T>意味着Option枚举中的Some变体可以包含任意类型的数据即可。下面是一些使用Option值包含数值类型和字符串类型的示例: 

let some_number = Some(5);
let some_string = Some("a string");

let absent_number: Option<i32> = None;

        假如我们使用了None而不是Some变体来进行赋值,那么我们需要明确地告知Rust这个Option<T>的具体类型。这是因为单独的None变体值与持有数据的Some变体不一样,编译器无法根据这些信息来正确推导出值的完整类型。

        当我们有了一个Some值时,我们就可以确定值是存在的,并且被Some所持有。而当我们有了一个None值时,我们就知道当前并不存在一个有效的值。这看上去与空值没有什么差别,那为什么Option<T>的设计就比空值好呢?

        简单来讲,因为Option<T>T(这里的T可以是任意类型)是不同的类型,所以编译器不会允许我们像使用普通值一样去直接使用Option<T>的值。例如,下面的代码在尝试将i8Option<i8>相加时无法通过编译: 

let x: i8 = 5;
let y: Option<i8> = Some(5);

let sum = x + y;

        运行这段代码,我们可以看到类似下面的错误提示信息:

error[E0277]: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is
not satisfied
 -->
  |
5 |     let sum = x + y;
  |                 ^ no implementation for `i8 + std::option::Option<i8>`
  |

        哇!这段错误提示信息实际上指出了Rust无法理解i8Option<T>相加的行为,因为它们拥有不同的类型。当我们在Rust中拥有一个i8类型的值时,编译器就可以确保我们所持有的值是有效的。我们可以充满信心地去使用它而无须在使用前进行空值检查。

        而只有当我们持有的类型是Option<i8>(或者任何可能用到的值)时,我们才必须要考虑值不存在的情况,同时编译器会迫使我们在使用值之前正确地做出处理操作。

        换句话说,为了使用Option<T>中可能存在的T,我们必须要将它转换为T。一般而言,这能帮助我们避免使用空值时最常见的一个问题:假设某个值存在,实际上却为空。 

        在编写代码的过程中,不必再去考虑一个值是否为空可以极大地增强我们对自己代码的信心。为了持有一个可能为空的值,我们总是需要将它显式地放入对应类型的Option<T>值中。

        当我们随后使用这个值的时候,也必须显式地处理它可能为空的情况。无论在什么地方,只要一个值的类型不是Option<T>的,我们就可以安全地假设这个值不是非空的。

        这是Rust为了限制空值泛滥以增加Rust代码安全性而做出的一个有意为之的设计决策。那么,当你持有了一个Option<T>类型的Some变体时,你应该怎样将其中的T值取出来使用呢?

        Option<T>枚举针对不同的使用场景提供了大量的实用方法,你可以在官方文档中找到具体的使用说明。熟练掌握Option<T>的这些方法将为你的Rust之旅提供巨大的帮助。

        总的来说,为了使用一个Option<T>值,你必须要编写处理每个变体的代码。某些代码只会在持有Some(T)值时运行,它们可以使用变体中存储的T

        而另外一些代码则只会在持有None值时运行,这些代码将没有可用的T值。match表达式就是这么一个可以用来处理枚举的控制流结构:它允许我们基于枚举拥有的变体来决定运行的代码分支,并允许代码通过匹配值来获取变体内的数据。

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1356890.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

双碳管理系统任务需求分析(第10套)

需求规格说明书 一、引言 &#xff08;一&#xff09;项目背景 编写本需求规格说明书的目的是为了详细呈现碳足迹产品需求和系统的功能描述&#xff0c;以进一步定制应用软件系统开发的细节问题&#xff0c;便于与项目开发协调工作。本文档面向的读者主要是项目委托单位的管…

找不到vcruntime140.dll怎么处理?6个修复教程分享

本文将详细介绍vcruntime140.dll文件的相关内容&#xff0c;并提供6个不同的修复教程&#xff0c;帮助大家解决这一问题。 一、vcruntime140.dll是什么文件&#xff1f; vcruntime140.dll是Visual C Redistributable Packages的一部分&#xff0c;它是Microsoft Visual Studi…

Alice Bob推出16量子比特量子处理单元——“Helium 1”

​&#xff08;图片来源&#xff1a;网络&#xff09; 容错量子计算机硬件开发商Alice & Bob宣布已成功流片一款新芯片“Helium 1”&#xff0c;希望能借助该芯片降低随着量子比特数增加而提高的错误率&#xff0c;这是该公司第一个纠错逻辑量子比特&#xff08;纠错量子计…

MySQL Enterprise版本各系统安装包下载

一、官方下载地址 oracle下载地址 https://edelivery.oracle.com/osdc/faces/SoftwareDelivery 使用oracle账号登录进去 Category选择Download Package(下载安装包)&#xff0c;搜索栏输入mysql Enterprise关键字点search进行搜索。选项结果第一个MySQL Enterprise Edition&a…

教程:Centos6迁移旧虚拟机文件后的网络配置教程,完美解决虚拟机移动后的网络ip变化问题

博主在工作后,想整整之前大学的虚拟机集群,因此特意从之前的旧电脑把虚拟机文件给拷贝了过来,在导入到vm-workstation,顺便能启动虚拟机后,发现之前的静态ip已经跟现在的宿主机网络不一样。想着重新配置,但觉得太麻烦,故想到了修改网卡的mac地址+网卡重配置方法,完美解…

【绘图软件】自用安装教程

链接&#xff1a;https://pan.baidu.com/s/17r9Pr460FzkULU7fTr91_w?pwdftv7 提取码&#xff1a;ftv7 --来自百度网盘超级会员V6的分享解压并且右键打开set up 解压crack软件前需要退出杀毒软件&#xff0c; 关闭实时保护 域网络关闭&#xff0c;专用网络关闭&#xff0…

【源码分析】 Calcite 处理流程详解:calcite架构、处理流程以及就一个运行示例进行源码分析

文章目录 一. Calcite整体架构二. Calcite处理流程三. 处理流程样例说明1. 样例demo1.1. 样例数据1.2. 使用calcite 2. 流程源码分析Step1: SQL 解析阶段&#xff08;SQL–>SqlNode&#xff09;Step2: SqlNode 验证&#xff08;SqlNode–>SqlNode&#xff09;1. 注册元数…

接口自动化测试,完整入门到入职篇

一、自动化测试 众所周知&#xff0c;自动化测试已经成为软件项目中不可或缺的测试方法。基于用户交互界面&#xff08;GUI&#xff09;的自动化测试方法具有模拟用户行为和过程可视化的特点&#xff0c;因此受到了广大入门自动化人士的喜爱。诸如&#xff1a;QTP、Selenium等…

JMeter 插件大全:详细介绍 Jmeter 常用插件

JMeter作为一个开源的接口性能测试工具&#xff0c;其本身的小巧和灵活性给了测试人员很大的帮助&#xff0c;但其本身作为一个开源工具&#xff0c;相比于一些商业工具&#xff08;比如 LoadRunner&#xff09;&#xff0c;在功能的全面性上就稍显不足。这篇博客&#xff0c;就…

深入了解 Vite:快速、简洁、高效的前端构建工具(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

MySQL数据库索引优化实战

目录 一、前言 二、准备工作 2.1 用户表&#xff08;TB_USER) 2.2 商品表&#xff08;TB_SKU) 2.3 订单表&#xff08;TB_ORDER&#xff09; 三、实例分析 3.1 索引提升查询性能 3.2 多表查询 3.3 索引失效 四、总结 一、前言 在数据库的应用中&#xff0c;性能优化…

【操作系统】输入输出系统

6.1 I/O系统的功能、模型和接口 I/O系统管理的主要对象是I/O设备和相应的设备控制器。其最主要的任务是&#xff0c;完成用户提出的I/O请求&#xff0c;提高I/O速率&#xff0c;以及提高设备的利用率&#xff0c;并能为更高层的进程方便地使用这些设备提供手段。 6.1.1 I/O系…

本地git服务器的使用

最后总结一句&#xff0c;用gitlab最省事&#xff0c;管理权限最方便&#xff0c;别像下文一样整了。 Windows上使用&#xff1a; 首先要在windows开发机上生成密钥&#xff1a; 1.安装git&#xff0c;首先去git官网下载git&#xff0c;https://git-scm.com/downloads&#xff…

2023年工作初体验

23年终于正式入职&#xff0c;参与了正式上线的电商平台、crm平台等项目的研发&#xff0c;公司规模较小&#xff0c;气氛融洽&#xff0c;没有任何勾心斗角、末位淘汰&#xff0c;几乎没什么压力。虽然是我的第一家公司&#xff0c;但实际是个适合养老的公司&#xff08;笑 总…

Web自动化测试:POM设计模式的实现

关于pom设计模式(project Object model/PageObject)&#xff0c;一种底层、逻辑、用例的分层&#xff0c;在项目还没有开发出来时&#xff0c;就可以开始写UI自动化脚本了&#xff0c;在开发完成后&#xff0c;再进行元素定位的适配以及调试&#xff1b;而且也可以多人共同维护…

清风数学建模笔记-主成分分析

内容&#xff1a;主成分分析 介绍&#xff1a; 主成分分析是一种降维算法&#xff0c;它通过旋转和变换将多个指标转化为少数几个主成分&#xff0c;这些主成分是原变量的线性组合&#xff0c;且互不相关&#xff0c;其能反映出原始数据的大部分信息。 例如解决多重共线性问题…

CSS 放大旋转动画

<template><div class"container" mouseenter"startAnimation" mouseleave"stopAnimation"><!-- 旋方块 --><div class"box" :class"{ rotate-scale-up: isAnimating }"><!-- 元素内容 -->&l…

不要盲目自学网络安全!学习顺序特别重要!

前言 一、什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防…

计算机视觉入门与调优

大家好啊&#xff0c;我是董董灿。 在 CSDN 上写文章写了有一段时间了&#xff0c;期间不少小伙伴私信我&#xff0c;咨询如何自学入门AI&#xff0c;或者咨询一些AI算法。 90%的问题我都回复了&#xff0c;但有时确实因为太忙&#xff0c;没顾得过来。 在这个过程中&#x…

金和OA jc6 ntko-upload 任意文件上传漏洞

产品简介 金和网络是专业信息化服务商&#xff0c;为城市监管部门提供了互联网监管解决方案&#xff0c;为企事业单位提供组织协同OA系统升开发平台&#xff0c;电子政务一体化平台智慧电商平合等服务 漏洞概述 金和OA jc6系统ntkoUpload接口处存在任意文件上传漏洞&#xf…