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.rhkb.cn/news/231333.html

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

相关文章

Minitab 21软件安装包下载及安装教程

Minitab 21下载链接&#xff1a;https://docs.qq.com/doc/DUkNHZVhwTXhtTFla 1.选中下载好的安装包&#xff0c;鼠标右键解压到”Minitab 21“文件夹 2.选中”Setup.exe“&#xff0c;鼠标右击选择“以管理员身份运行” 3.点击“下一步” 4.点击“是” 5.点击“下一步” 6.勾选…

ChatGPT绘制全球植被类型分布图、生物量图、土壤概念图、处理遥感数据并绘图、病毒、植物、动物细胞结构图

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

信息系统安全——缓冲区溢出和恶意代码分析

实验 1 缓冲区溢出和恶意代码分析 1.1 实验名称 《缓冲区溢出和恶意代码分析》 1.2 实验目的 1 、熟练使用恶意代码分析工具 OD 和 IDA 2 、通过实例分析&#xff0c;掌握缓冲区溢出的详细机理 3 、通过实例&#xff0c;熟悉恶意样本分析过程 1.3 实验步骤及内容 第一阶段&…

ClickHouse基础知识(五):ClickHouse的SQL 操作

基本上来说传统关系型数据库&#xff08;以 MySQL 为例&#xff09;的 SQL 语句&#xff0c;ClickHouse 基本都支持&#xff0c; 这里不会从头讲解 SQL 语法只介绍 ClickHouse 与标准 SQL&#xff08;MySQL&#xff09;不一致的地方。 1. Insert 基本与标准 SQL&#xff08;My…

71内网安全-域横向网络传输应用层隧道技术

必备知识点&#xff1b; 代理和隧道技术的区别&#xff1f; 代理主要解决的是网络访问问题&#xff0c;隧道是对过滤的绕过&#xff0c; 隧道技术是为了解决什么 解决被防火墙一些设备&#xff0c;ids&#xff08;入侵检测系统&#xff09;进行拦截的东西进行突破&#xff0…

迪杰斯特拉(Dijkstra)算法详解

【专栏】数据结构复习之路 这篇文章来自上述专栏中的一篇文章的节选&#xff1a; 【数据结构复习之路】图&#xff08;严蔚敏版&#xff09;两万余字&超详细讲解 想了解更多图论的知识&#xff0c;可以去看看本专栏 Dijkstra 算法讲解&#xff1a; 迪杰斯特拉算法(Di…

pip install skopt安装显示没有对应版本问题及解决

一、问题描述以及分析 &#xff08;一&#xff09;问题描述 ModuleNotFoundError: No module named skopt pip install skopt Note: you may need to restart the kernel to use updated packages.ERROR: Could not find a version that satisfies the requirement skopt (fro…

C语言编译器(C语言编程软件)完全攻略(第十三部分:VS2010使用教程(使用VS2010编写C语言程序))

介绍常用C语言编译器的安装、配置和使用。 十三、VS2010使用教程&#xff08;使用VS2010编写C语言程序&#xff09; 提示&#xff1a;VS2010 可以在 XP、Win7 和 Win8 下完美运行&#xff0c;但在 Win10 下可能会有兼容性问题&#xff0c;使用 Win10 的读者建议安装 VS2015 或…

JDBC练习查询所有内容

MySql表代码 -- 删除tb_brand表 drop table if exists tb_brand; -- 创建tb_brand表 create table tb_brand (-- id 主键id int primary key auto_increment,-- 品牌名称brand_name varchar(20),-- 企业名称company_name varchar(20),-- 排序字段ordered int…

解决VNC连接Ubuntu服务器打开终端出现闪退情况

服务器环境 阿里云ECS服务器 操作系统&#xff1a;Ubuntu 20.0.4 如何使用VNC连接阿里云ECS服务器 1.阿里云官方指导&#xff1a;通过VNC搭建Ubuntu 18.04和20.04图形界面 2.新手入门ECS——ubuntu 20.04安装图形化界面和本地VNC连接 问题描述 使用VNC连接上新申请阿里云服…

C# 如何读取Excel文件

当处理Excel文件时&#xff0c;从中读取数据是一个常见的需求。通过读取Excel数据&#xff0c;可以获取电子表格中包含的信息&#xff0c;并在其他应用程序或编程环境中使用这些数据进行进一步的处理和分析。本文将分享一个使用免费库来实现C#中读取Excel数据的方法。具体如下&…

LLM之RAG实战(九)| 高级RAG 03:多文档RAG体系结构

在RAG&#xff08;检索和生成&#xff09;这样的框架内管理和处理多个文档有很大的挑战。关键不仅在于提取相关内容&#xff0c;还在于选择包含用户查询所寻求的信息的适当文档。基于用户查询对齐的多粒度特性&#xff0c;需要动态选择文档&#xff0c;本文将介绍结构化层次检索…

实现文件拖拽上传的功能

1 先来看一下效果 2 我们来看一下代码执行的结果&#xff1a; 我们创建目标的容器盒子 和可以展示数据的ul 监听进入目前盒子的事件 3 文件进入目标容器中解析文件

使用echarts制作柱状图并且下方带表格

实现效果: 调试地址: Examples - Apache ECharts 源码: option { title: { left: center, top: 0, text: 2022-05月 制造产量 达成情况(单位: 吨) (图1)\n\n集团目标产量: 106,675吨 集团实际产量: 2,636吨, textStyle:{ fontSize:20, colo…

3D Gaussian Splatting复现

最近3D Gaussian Splatting很火&#xff0c;网上有很多复现过程&#xff0c;大部分都是在Windows上的。Linux上配置环境会方便简单一点&#xff0c;这里记录一下我在Linux上复现的过程。 Windows下的环境配置和编译&#xff0c;建议看这个up主的视频配置&#xff0c;讲解的很细…

QT基础知识

QT基础知识 文章目录 QT基础知识1、QT是什么2、Qt的发展史3、为什么学习QT4、怎么学习QT1、工程的创建(环境的下载与安装请百度&#xff09;2、创建的工程结构说明3、怎么看帮助文档1、类使用的相关介绍2. 查看所用部件&#xff08;类&#xff09;的相应成员函数&#xff08;功…

【LeetCode】修炼之路-0001-Two Sum(两数之和)【python】【简单】

前言 计算机科学作为一门实践性极强的学科,代码能力的培养尤为重要。当前网络上有非常多优秀的前辈分享了LeetCode的最佳算法题解,这对于我们这些初学者来说提供了莫大的帮助,但对于我这种缺乏编程直觉的学习者而言,这往往难以消化吸收。&#xff08;为什么别人就能想出这么优雅…

牛刀小试 - C++实现贪吃蛇

参考文档 借鉴了这位大佬的博客及代码&#xff0c;键入代码后发现有很多报错&#xff0c;依次解决后成功运行 c 实现贪吃蛇&#xff08;含技术难点解析和完整代码&#xff09; 技术点&#xff1a; C中_kbhit()函数与_getch()函数 Windows API 坐标结构 COORD 句柄 HANDLE 获…

有能力,但是不赚钱,往往是因为没有这三个能力!2024最适合创业的细分行业,2024最适合创业的行业

很多人非常有能力&#xff0c;在学校是学霸&#xff0c;在公司是高管&#xff0c;但是出来自己创业就不行了。觉得是自己的能力不够&#xff0c;其实不是你的能力不够&#xff0c;而是你欠缺下面这三种能力。如果你能掌握这三种能力&#xff0c;就算之前是普通人尝试创业&#…

Hotspot源码解析-第十二章-线程栈保护页

了解保护页&#xff0c;先从几个问题开始吧 1、为什么线程栈有栈帧了&#xff0c;还要有保护页&#xff1f; 答&#xff1a;在操作系统中内存可以看成是一个大数组&#xff0c;这就有一个问题&#xff0c;线程之间可能会互相踩了别人的内存空间&#xff0c;所以栈空间也存在这…