014、枚举与模式匹配

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

        接着,我们会讨论一个特别有用的枚举: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.mzph.cn/news/596405.shtml

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

相关文章

提升设计效率:全面了解如何使用Figma插件

Figma组件库包括颜色、字体、图标、按钮、阴影、圆角、间距等。当Figma组件库的样式和Figma组件达到一定数量时&#xff0c;将难以维护&#xff0c;设计和开发的对接成本将大大提高。Figma可以在同一母版下单独设置样式&#xff0c;而不影响与母版之前的关系&#xff0c;这是Sk…

9.java——(杂例)组合,代理,向上转型static,fianl,关键字(有道云笔记复制粘贴,大家整体性的把握)

组合——内部有类&#xff08;心中有对象&#xff01;&#xff01;&#xff01;&#xff09;&#xff08;足球 和足球运动员梅西和脚下的足球一样&#xff09; has和is的区别&#xff0c;has是组合&#xff0c;是有&#xff0c;持有的意思&#xff1b;is是继承&#xff0c;是…

C++八股学习心得.3

1.C 数组 C 支持数组数据结构&#xff0c;它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量。所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素&#xff0c;最高的地址对应最后一个…

实时计算大作业kafka+zookeeper+storm+dataV

第一章 总体需求 1.1.课题背景 近年来&#xff0c;大数据称为热门词汇&#xff0c;大数据分析随着互联网技术的发展愈加深入电商营销之 中&#xff0c;越来越多的电商企业利用大数据分析技术&#xff0c;利用信息化对产业发展营销方向进行确定&#xff0c; 对电子商务行…

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

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

git rebase(变基)应用场景

文章目录 git rebase(变基)应用场景1.git rebase -i HEAD~3 git rebase(变基)应用场景 使得提交记录变得简洁 现在我们模拟我们有多次提交记录&#xff0c;本地仓库有三条提交 整合成一条提交记录 1.git rebase -i HEAD~3 提交记录合并 HEAD~3合并三条记录 执行之后 然后把…

事实就是这么残酷,分享一个案例投资者是怎么一步步失败

都说交易市场要学会斗智斗勇&#xff0c;但fpmarkets澳福提醒交易者要始终记住&#xff0c;买的没有卖的精&#xff0c;下面就分享一个案例&#xff0c;让各位投资者知道现实就是这么残酷&#xff0c;一些无良的资本是怎么一步步让投资者失败的。 当在整个交易市场中渐渐地&am…

清风数学建模笔记-时间序列分析

内容&#xff1a;时间预测分析 一.时间序列 1.时点时间序列 2.时期时间序列&#xff1a;可相加 二.时间趋势分解 1.季节趋势 拓展&#xff1a;百度指数&#xff1a; 2.循环变动趋势&#xff08;和季节很像但是是以年为单位&#xff09; 3.不规则变动趋势&#xff08;像扰…

《深入理解C++11:C++11新特性解析与应用》笔记八

第八章 融入实际应用 8.1 对齐支持 8.1.1 数据对齐 c可以通过sizeof查询数据的长度&#xff0c;但是没有对对齐方式有关的查询或者设定进行标准化。c11标准定义的alignof函数可以查看数据的对齐方式。 现在的计算机通常会支持许多向量指令&#xff0c;4组8字节的浮点数据&a…

离线Vscode 安装完成后 添加到右键菜单

复制下面代码&#xff0c;修改文件后缀名为&#xff1a;reg Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\*\shell\VSCode] "Open with Code" "Icon""D:\\_Porgram_IT\\VsCode\\Code.exe"[HKEY_CLASSES_ROOT\*\shell\VSCode\comman…

AI:110-基于深度学习的药物分子结构生成与预测

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的关键代码,详细讲解供…

填充点云孔洞(较大的洞)halcon算法

前言 很多时候,一些小洞可以通过平滑算法,或者三角化算法的参数调整,即可对较小的孔洞进行填充,但是较大的洞却很难通过上面的算法进行填充。 下面介绍一种填充孔洞的思路: 步骤一:对点云进行滤波处理,找到孔洞所在平面 本文为了更直观的进行讲解,去掉了去除噪声和…

数读中国这十年:研发经费超3万亿元 创新引领显成效

摘要&#xff1a;本文转载自新华社。 新华社北京12月22日电 题&#xff1a;研发经费超3万亿元 创新引领显成效 新华社记者 陈炜伟、潘德鑫 这是2023年6月11日在青海省海西蒙古族藏族自治州格尔木市拍摄的一座光热电站&#xff08;无人机照片&#xff09;。新华社记者 张宏…

Linux高级玩家必备sos_report-尚文网络xUP楠哥

进Q群11372462领取专属报名福利! # 什么是sos sos 是红帽技术支持工程师在执行任务时的常见起点分析 RHEL 系统的服务请求。 该实用程序提供了一种标准化的方式来收集红帽支持工程师可以在整个调查过程中参考的诊断信息支持案例中报告的问题。 使用 sos 报告实用程序有助于确…

亚马逊站内广告位置在哪设置?怎么设置广告位置?-站斧浏览器

亚马逊站内广告位置在哪设置&#xff1f; 亚马逊提供了多种广告类型&#xff0c;包括&#xff1a; Sponsored Products&#xff08;赞助产品&#xff09;&#xff1a;在搜索结果和商品详情页中展示。 Sponsored Brands&#xff08;赞助品牌&#xff09;&#xff1a;在搜索结…

Fast R-CNN

Fast R-CNN算法流程 对比与R-CNN其在第二步时并没有将所有的候选区域进行逐个的CNN特征提取&#xff0c;而是直接将整个图片进行一次CNN特征提取&#xff0c;让后再将候选区映射到feature map上。可想而知速度得到了提升。这里的ROI pooling层缩放到7x7就是将候选区域对应的特征…

通过聚道云软件连接器实现销帮帮软件与i人事软件的智能对接

客户介绍 某软件行业公司是一家专业从事软件技术服务、软件开发、应用解决方案、业务流程优化、专业服务的高科技企业。公司拥有一支经验丰富、技术精湛的服务团队&#xff0c;具备多年的软件开发和应用解决方案经验。他们不断追求技术的创新和进步&#xff0c;以满足客户不断…

Sectigo与Geotrust ov多域名证书的区别

Sectigo和Geotrust都是比较知名的CA认证机构。其中&#xff0c;Sectigo原名Comodo&#xff0c;在2018年整合SSL证书业务&#xff0c;改名为Sectigo&#xff0c;旗下的SSL证书产品根证书也变为Sectigo。Geotrust则是另一个备受信任的数字证书品牌&#xff0c;现在是Digicert旗下…

C语言注意点(4)

1、void *a是什么意思 答&#xff1a;泛型指针&#xff0c;但不规定其类型(就是地址确定&#xff0c;但数据长度不确定)在动态分配内存时&#xff0c;malloc的返回值就是该类型&#xff0c;方便用户进行强制转换。 2、VS怎么一键规范格式 for(i0;i<10;i)enter后&#xff0c;…

JUC AQS(AbstractQueuedSynchronizer)

文章目录 AQS &#xff08;AbstractQueuedSynchronizer^1.5^&#xff09;CLH 锁队列AbstractQueuedSynchronizer 成员变量说明AbstractQueuedSynchronizer.Node 源码CLH 队列原理图入队逻辑方法出队逻辑方法 继承 AQS 需要实现的几个方法AQS 对象序列化ReentrantLock 源码解析R…