Rust中的枚举和模式匹配

news2024/10/5 19:18:45

专栏简介:本专栏作为Rust语言的入门级的文章,目的是为了分享关于Rust语言的编程技巧和知识。对于Rust语言,虽然历史没有C++、和python历史悠远,但是它的优点可以说是非常的多,既继承了C++运行速度,还拥有了Java的内存管理,就我个人来说,还有一个优点就是集成化的编译工具cargo,语句风格和C++极其相似,所以说我本人还是比较喜欢这个语言,特此建立这个专栏,作为学习的记录分享。

日常分享:每天努力一点,不为别的,只是为了日后,能够多一些选择,选择舒心的日子,选择自己喜欢的人!


前面我们提到过,Rust中没有了switch case这种模式控制语句,但是喃,除此之外,却又多了另一种匹配规则,那就是模式匹配。所以这节我们就来聊聊模式匹配这种匹配机制。

1、枚举

枚举其实在c++中就有过定义,二者相差不大,关键字是enum,枚举和结构体一样,也是用来自定义的数据类型。

说到枚举,可能有些同学还不是特别清楚,枚举的意义在那里,其实枚举他只是一个存放字段的一种容器吧,在后面的代码中,如果你需要多种字段,但是你又不是特别明确具体需要哪些,就把所有可能的字段放在其中,需要什么就使用什么。

enum Error{
  typeError,
  lengthError,
}

 例如上面的代码,定义了一个Error的枚举类型,这个时候,Error就是一个数据类型。

1.1、枚举值

let oneError = Error::typeError;
  let twoEroor = Error::lengthError;

 就如上面的代码,我们定义了两个实例对象,而他们的数值则是Error中的两个字段。

这里注意的是,枚举的成员位于其标识符的命名空间中,并使用两个冒号分开。

但是有人看到这里就会有疑惑,这里的枚举类型中的字段都是没有具体值的,那么我们如何将值与枚举成员关联?

上一节讲解了结构体的概念,这里我们就可以使用结构体来进行绑定:


enum Error{
  typeError,
  lengthError,
}

struct getError{
  oneError: Error,
  twoEroorderError: Error,
  address:String,
}
let amples = getError{
  oneError: Error::typeError,
  twoEroorderError: Error::lengthError,
  address:String::from("Hello world"),
};

 上面所时代码就是将枚举作为结构体的一部分,除了上面的方法,我们似乎还可以使用其他方法,例如将数据放进每一个枚举成员。

enum Error{
  typeError(String),
  lengthError(String),
}
let oneError = Error::typeError(String::from("one error"));
let twoEroor = Error::lengthError(String::from("two Eroor error"));

1.2、Option枚举

前面写的代码中,对于枚举数据类型,虽然将值通过结构体进行了绑定,但是却没有具体的值,只有通过将值放进枚举成员,才能获得值。那么没有的值又是什么?或者说又有什么作用?

Rust语言和其他语言的一点不同就在于它没有空值,也就是说不能赋予空值,必须去实现。Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。

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

 Option<T> 枚举是如此有用以至于它甚至被包含在了 prelude 之中,你不需要将其显式引入作用域。另外,它的成员也是如此,可以不需要 Option:: 前缀来直接使用 SomeNone。这里要注意,Option枚举是含在标准库的,不需要我们定义,直接使用,上面知识给出参考。

<T> 语法是一个我们还未讲到的 Rust 功能。它是一个泛型类型参数,后面遇到了我们再详细介绍。

  let some_number = Some(5);
  let some_char = Some('e');
  let number:Option<i32>=None;

上面的三条语句,变量some_number的类型是i32,some_char的类型是char,而number的类型是i32,只是是一个空值。 对于Option<T>,这里的T可以是任何数据类型,除了赋空值外,一般来说不需要注明变量的数据类型,除非是特殊需要,Rust可以推断其变量的数据类型。如果是赋空值,就必须注明变量的数据类型,否则会报错。

不过这里需要注意的是,Option<T>标注的数据类型与相同的数据类型变量不能进行运算.

  let one_num:Option<i32>=some(20);
  let s:i32=30;
  println!("{}",one_num+s);

error[E0425]: cannot find function `some` in this scope
  --> src/main.rs:45:27
   |
45 |   let one_num:Option<i32>=some(20);
   |                           ^^^^ help: a tuple variant with a similar name exists (notice the capitalization): `Some`

error[E0369]: cannot add `i32` to `Option<i32>`
  --> src/main.rs:47:24
   |
47 |   println!("{}",one_num+s);
   |                 -------^- i32
   |                 |
   |                 Option<i32>

Some errors have detailed explanations: E0369, E0425.
For more information about an error, try `rustc --explain E0369`.
error: could not compile `number` due to 2 previous errors

当运行上述代码的时候就会出现这种报错,这是为什么喃?这是由于当我们使用Option<T> 数据类型的时候就表明该数据可能为空,而我们使用i32(或其他数据类型)的时候,就已经表明改变两不可能为空值,所以才会出现报错,根本原因还是在与其数据类型被系统判定为两种数据类型.

换句话说,在对 Option<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 表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。

2、match控制流结构

我们前面说了在Rust语言中没有switch这种控制流语句,但是它却推出了match这种强大的控制流运算符。在python语言中,match是正则表达式中的匹配函数,所以这里也可以理解为匹配函数。

先来看个例子:

enum error_message{
  E0425,
  E0369,
  E2345,
}
fn get_error_message(message:error_message)->u32{
  match message{
    error_message::E0425 =>{
      println!("cannot find function `some` in this scope");
      return 0;
    }
    error_message::E0369=>{
      println!("cannot add `i32` to `Option<i32>");
      return 1;
    }
    error_message::E2345=>{
      println!("could not compile `number` due to 2 previous errors");
      return 2;
    }
  }
}
fn main(){
  let mut error=get_error_message(error_message::E2345);
  println!("{}",error);
  get_error_message(error_message::E0425);
}

就如上面的代码,先是定义了一个枚举数据类型,然后定义了一个函数,在函数中使用了match控制流。根据不同的参数值,返回不同的值,并打印出结果。

1.1、绑定值模式

匹配分支的另一个有用的功能是可以绑定匹配的模式的部分值。这也就是如何从枚举成员中提取值的。

/*
enum error_message{
  E0425,
  E0369,
  E2345,
}
fn get_error_message(message:error_message)->u32{
  match message{
    error_message::E0425 =>{
      println!("cannot find function `some` in this scope");
      return 0;
    }
    error_message::E0369=>{
      println!("cannot add `i32` to `Option<i32>");
      return 1;
    }
    error_message::E2345=>{
      println!("could not compile `number` due to 2 previous errors");
      return 2;
    }
  }
}
fn main(){
  let mut error=get_error_message(error_message::E2345);
  println!("{}",error);
  get_error_message(error_message::E0425);
}
*/
#[derive(Debug)]
enum UsState{
  Alabama,
  Alaska,
}
enum Coin{
  Penny,
  Nickel,
  Dime,
  Quarter(UsState),
}
fn value_cents(coin:Coin) -> usize {
  match coin{
    Coin::Penny =>{
      return 1;
    }
    Coin::Nickel =>{
      return 5;
    }
    Coin::Dime =>{
      return 10;
    }
    Coin::Quarter(state)=>{
      println!("State quarter from {:#?}",state);
      return 25;
    }
  }
}
fn main()
{
  let b=UsState::Alaska;
  let c=Coin::Quarter(b);
  value_cents(c);

}

 如果调用 value_in_cents(Coin::Quarter(UsState::Alaska))coin 将是 Coin::Quarter(UsState::Alaska)。当将值与每个分支相比较时,没有分支会匹配,直到遇到 Coin::Quarter(state)。这时,state 绑定的将会是值 UsState::Alaska。接着就可以在 println! 表达式中使用这个绑定了,像这样就可以获取 Coin 枚举的 Quarter 成员中内部的州的值。

1.2、匹配Option<T>

我们在之前的部分中使用 Option<T> 时,是为了从 Some 中取出其内部的 T 值;我们还可以像处理 Coin 枚举那样使用 match 处理 Option<T>!只不过这回比较的不再是硬币,而是 Option<T> 的成员,但 match 表达式的工作方式保持不变。

例如:

fn plus_amount(amount:Option<i32>)->Option<i32> 
{
  match amount 
  {
    None=>{
      println!("该值为空值");
      return None;
    }
    Some(i)=>{
      println!("该值不是空值,值为{}",i);
      return Some(i);
    }
  }
}
fn main(){
  let five=Some(5);
  let num1=plus_amount(five);
  plus_amount(None);
}

1.3、通配模式和_占位符

其实除了枚举,match控制流也可以用于其他形式,比如:

let num=30;
  match num{
    10=>{
      println!("10");
    }
    11 => println!("11"),
    other => println!("other"),
  }

 上面的代码中,我们在最后使用了other这个变量,这个变量覆盖了所有其他的可能值,除了我们列出来的可能性,other会包含所有的其他可能性,所以other一定要放在最后,否无法达到目的。

不过在这里因该有人发现了,other会绑定match匹配的值,这个我们将上面的程序更改一下,就能的出这个结论:

fn main(){
  let five=Some(5);
  let num1=plus_amount(five);
  plus_amount(None);
  let num=30;
  match num{
    10=>{
      println!("10");
    }
    11 => println!("11"),
    other => println!("other的值为:{}",other),
  }
}

 other的值为:30

上面就是输出结果,这说明other绑定到了match匹配的值上,这样做的好处就是可以获得匹配值,将其进行使用,但是如果我们不需要使用那个值,这样做就有点浪费,所以Rust也推出了_占位符,占位符只是表示可以匹配任意值而不能绑定到该值。

fn main(){
  let five=Some(5);
  let num1=plus_amount(five);
  plus_amount(None);
  let num=30;
  match num{
    10=>{
      println!("10");
    }
    11 => println!("11"),
    _ => println!("other"),
  }
}

 3、if let间接控制流

Rust中的if let控制流说的简单点,就相当于c++中的if else语句,对于一些简单的判别,使用if let控制流语句将会简单很多。例如:

fn main() 
{
  let config=Some(3u8);
  match config{
    Some(max)=>println!("{}",max),
    _=>(),
  }
}

上面面代码的意思是,匹配config的值,如果值是Some,就将值绑定到max变量上,然后输出,否则就忽略。

除了上面这样的方式,我们还可以使用其他的方式:

fn main() 
{
  let config=Some(3u8);
 if let Some(max)=config{
  println!("{}",max);
 }
}

这样看来是不是就简单的多了,所以说从某种角度来看,if let语句确实简单了很多。

#[derive(Debug)]
enum UsState{
  Alabama,
  Alaska,
}
enum Coin{
  Penny,
  Nickel,
  Dime,
  Quarter(UsState),
}
fn main() 
{
  let mut count=0;
  let b=UsState::Alaska;
  let coin=Coin::Quarter(b);
  //以下两种方式都可以
  /*
  match coin{
    Coin::Quarter(state)=>{
      println!("State quarter from {:#?}",state);
    }
    _=>count+=1,
  }*/
  if let Coin::Quarter(state) = coin{
    println!("State quarter from {:#?}",state);
  }
  else{
    count+=1;
  }
}

4、总结

现在我们涉及到了如何使用枚举来创建有一系列可列举值的自定义类型。我们也展示了标准库的 Option<T> 类型是如何帮助你利用类型系统来避免出错的。当枚举值包含数据时,你可以根据需要处理多少情况来选择使用 matchif let 来获取并使用这些值。

你的 Rust 程序现在能够使用结构体和枚举在自己的作用域内表现其内容了。在你的 API 中使用自定义类型保证了类型安全:编译器会确保你的函数只会得到它期望的类型的值。

下一节我们学习模块系统。

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

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

相关文章

24 mysql all 查询

前言 这里主要是 探究一下 explain $sql 中各个 type 诸如 const, ref, range, index, all 的查询的影响, 以及一个初步的效率的判断 这里会调试源码来看一下 各个类型的查询 需要 lookUp 的记录 以及 相关的差异 此系列文章建议从 mysql const 查询 开始看 测试表结构…

【网络】路由器和交换机的区别

&#x1f341; 博主 "开着拖拉机回家"带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——&#x1f390;开着拖拉机回家_Linux,大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341; 希望本文能够给您带来一定的帮助&#x1…

小谈设计模式(21)—迭代器模式

小谈设计模式&#xff08;21&#xff09;—迭代器模式 专栏介绍专栏地址专栏介绍 迭代器模式对象分析聚合对象&#xff08;Aggregate&#xff09;迭代器对象&#xff08;Iterator&#xff09; Java程序示例程序分析12 优缺点分析优点简化了聚合对象的接口统一的遍历方式增加了代…

20秒基于Chat GPT完成工作中的小程序

1. 写在前面 GPT自从去年爆发以来&#xff0c;各大公司在大模型方面持续发力&#xff0c;行业大模型也如雨后春笋一般发展迅速&#xff0c;日常工作中比较多的应用场景还是问答模式&#xff0c;作为写程序的辅助也偶尔使用。今天看到一篇翻译的博客“我用 ChatGPT&#xff0c;…

更新Xcode 版本后运行项目出现错误 Unable to boot the Simulator 解决方法

错误截图 出现 Unable to boot the Simulator 错误原因很多&#xff0c;以下方法不一定都适用&#xff0c;我是通过以下方法解决的 打开命令终端输入以下命令&#xff0c;可能需要你输入开机密码 sudo rm -rf ~/Library/Developer/CoreSimulator/Caches

Android---字节码层面分析Class类文件

Java 提供了一种可以在所有平台上都能使用的一种中间代码---字节码文件(.class文件)。有了字节码&#xff0c;无论是那个平台只要安装了虚拟机都可以直接运行字节码文件。有了虚拟机&#xff0c;解除了 java 虚拟机与 java 代码之间的耦合。 Java 虚拟机当初被设计出来时就不单…

【设计模式】访问者模式

文章目录 1.访问者模式定义2.访问者模式的角色3.访问者模式实战案例3.1.场景说明3.2.UML类图3.3.代码实现 4.访问者模式优缺点5.访问者模式适用场景6.访问者模式总结 主页传送门&#xff1a;&#x1f481; 传送 1.访问者模式定义 访问者模式&#xff08;Visitor Pattern&#x…

【MATLAB源码-第43期】基于matlab的turbo码误码率仿真比较不同迭代次数,采用logmap/sova算法。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 Turbo码是一种前向纠错码 (Forward Error Correction, FEC)&#xff0c;在 1993 年由法国的两位研究员 Claude Berrou 和 Alain Glavieux 提出。这种编码技术以其接近 Shannon 极限的高性能而受到广泛关注。以下是关于 Turbo…

黑马JVM总结(二十六)

&#xff08;1&#xff09;异常-catch 下面看一下字节码里面怎么做异常的处理 &#xff08;2&#xff09;异常-多个catch astore_2:就是把异常对象的地址存到局部变量表2号曹位上 &#xff08;3&#xff09;异常-multicatch &#xff08;4&#xff09;异常-finally &#xff0…

uniapp 实现地图头像上的水波纹效果

最近实现了uniapp 地图头像水波纹的效果&#xff0c;话不多说&#xff0c;先来看看视频效果吧&#xff1a;链接 在这里具体的代码就不放出来了&#xff0c;还是利用了uniapp的 uni.createAnimation 方法&#xff0c;因为cover-view 不支持一些css 的动画效果&#xff0c;所以这…

探秘布隆过滤器:高效数据查找与去重利器

探秘布隆过滤器&#xff1a;高效数据查找与去重利器 引言 在现代计算机科学中&#xff0c;数据的查找与去重是一个至关重要的问题。本文将介绍一种高效的数据结构——布隆过滤器&#xff0c;它能够在海量数据中快速判断某个元素是否存在&#xff0c;同时具有出色的空间效率。…

动态调整系统主题色(4): CssVar 与 Variant 方案的探索

动态调整系统主题色(4): CssVar 与 Variant 方案的探索 动态调整系统主题色(4): CssVar 与 Variant 方案的探索 前言方案的介绍与比较 CssVar (CSS 变量方案)CSS 变量方案与 tailwindcss 的结合Variant 方案 2种方案在小程序上的示例之前的几篇 前言 这篇已经是动态调整系统…

深度学习模型部署与优化:策略与实践;L40S与A100、H100的对比分析

★深度学习、机器学习、生成式AI、深度神经网络、抽象学习、Seq2Seq、VAE、GAN、GPT、BERT、预训练语言模型、Transformer、ChatGPT、GenAI、多模态大模型、视觉大模型、TensorFlow、PyTorch、Batchnorm、Scale、Crop算子、L40S、A100、H100、A800、H800 随着生成式AI应用的迅猛…

python打开浏览器并模拟搜索

打开已存在的浏览器 打开已存在的浏览器有个很重要的作用就是&#xff0c;可以对于一些登录场景&#xff0c;提前登录好&#xff0c;不需要模拟登录了。 在命令行中执行打开chrome的命令&#xff0c;在图标上找到chrome的安装位置 在cmd命令行下执行命令 C:\Program Files\…

工厂管理软件中的计划排产是什么

一、计划排产的定义&#xff1a; 计划排产是指根据工厂的生产能力、订单需求和资源限制等因素&#xff0c;合理安排生产任务和时间&#xff0c;以实现高效的生产计划。它涉及到生产订单的分配、生产线的调度和资源的优化利用&#xff0c;旨在提高生产效率、缩短交货时间和降低…

高速公路堵车动力学

S/t trace 图可以分析牛顿力学时间序列的一切。 下例分析了当车距太小时&#xff0c;一个轻微的刹车扰动如何触发大堵车的&#xff1a; 堵车由以下因素促成&#xff1a; 刹车反应时间&#xff0c;刹车反应很快&#xff0c;看见灯即可&#xff0c;即使这样越往后刹车必须越狠&…

对于对象初始化的加深理解

一道有一定难度和挑战性的注重细节的有趣的面试题 目录 案例需求不同写法与角度下写法一写法二写法三写法四A类B类测试类 注参考视频 案例需求 不同写法与角度下 写法一 注&#xff1a;方法的修饰符为private package com.xie.interview;/*** 对象属性初始化相关的面试笔试题*…

Stable diffusion的架构解读(本博客还是以unet架构为主)

博客只是简单的记录一下自己学的&#xff0c;基于自己的一些情况&#xff0c;所以简单了一些只是将来忘记&#xff0c;用来回顾用。 论文的大体框架 unet结构位于 unet会接受prompt特征、latent特征、和t时间步特征&#xff0c;最后生成新一轮的特征 可以参考知乎大佬htt…

re学习(37)DASCTF 2023 0X401七月暑期挑战赛 controflow

程序通过改变栈里面的返回地址来控制程序的控制流 从而达到混淆的效果 左侧有许多被hook的函数 在每个函数开头设置断点 然后观察程序的运行流程 会发现输入的数据会进行 异或 相加 异或 相减 相乘 异或等操作 要注意部分运算的索引是 从[10]开始的 具体思路参考&#xf…

三十、【进阶】B-Trees的演变过程

1、索引结构 &#xff08;1&#xff09;二叉树 &#xff08;2&#xff09;B-Tree树 B-Tree树最大度数为5&#xff0c;代表每一个节点最多存储4个key(每个节点最多存储4个数据)&#xff0c;5个指针(可以指向5个子节点)。 2、演变过程&#xff08;最大度数为5&#xff09; &…