【Rust】第七节:枚举与模式匹配

2024-01-26 13:04

本文主要是介绍【Rust】第七节:枚举与模式匹配,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0 前言

是我的一点学习笔记,因为本身内容就不多、不复杂,所以这一篇内容结构与原文基本一致,但是是我个人理解原文的一个思路过程的记录。

枚举,enums,如果你了解tspythonc/cppjava那你可能会觉得很熟悉,但是又很不同,rust的枚举更丰富、更灵活、更方便、更强大。
所以你准备走进rust的枚举类型了吗?

原文链接:Rust程序设计语言


1 枚举

rust有两种枚举,一个是enum,一个是option,我们一个一个来看。

1.1 enum

以IPv4和IPv6为例

// 以下是伪代码,不可直接运行
// 1 定义enum
enum IpAddrKind {V4,V6,
}
// 2 实例化enum
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
// 3 函数可以以enum为入参
fn route(ip_type: IpAddrKind) { }
// 4 使用函数
route(IpAddrKind::V4);
route(IpAddrKind::V6);

以上是最简单的写法,那么在此之上,我们是否可以将enum与struct结合起来,从而实现更加复杂的enum类型呢?显然是可以的

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"),
};

而事实上,我们没有必要把枚举作为结构体的一部分,而是可以直接给枚举成员赋值,就像下面这样

enum IpAddr {V4(String),V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));

而更加有趣的是,enum可以存储不同的类型!比如像这样

enum IpAddr {V4(u8, u8, u8, u8),V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));

实际上,标准库也提供了一个开箱即用的定义,让我们来看看学习一下——

struct Ipv4Addr {// --snip--
}
struct Ipv6Addr {// --snip--
}
enum IpAddr {V4(Ipv4Addr),V6(Ipv6Addr),
}

既然enum能存储不同的类型,那自然也能存储不同的struct,这种方式大大地拓展了enum的灵活性和可用性
还有更有趣的!在上一节中,我们说了struct可以使用impl,同样的,enum也能使用imple,这意味着你能实现这样的效果——

enum Message {Quit,Move { x: i32, y: i32 },Write(String),ChangeColor(i32, i32, i32),
}
impl Message {fn call(&self) {// 在这里定义方法体}
}
let m = Message::Write(String::from("hello"));
m.call();

1.2 option

optionrust提供的另一种枚举值,为什么要option?是为了解决空值(null)问题。
你可能在其他语言的开发中,经常遇到空值引起的各种问题。而rust中,没有空值,所以不会有空值问题。
但是rust提供了一个可以编码存在或不存在的概念的枚举,这是如何实现的呢?让我们看看标准库——

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

1、这里的<T>是泛型语法,你可能在别的语言中已经接触过了,这里先不展开。
2、Some就是,存在一个值;None,就是并没有一个有效的值,与空值起到同样的作用。
3、Option<T><T>是不同的类型!所以他们不允许像对一个有效的<T>那样处理Option<T>,也就避免了问题。
是不是听起来有点拗口?让我们看看这个例子

let x: i8 = 5;
let y: Option<i8> = Some(5);let sum = x + y;// 编译器报错

是的,通过此种方式,避免了我们把一个无效的值当成一个有效的值去处理,所以我们可以安全地处理值,并且信赖他绝对不会是空值!而且,在你处理这个值时,需要显式地生命当他为空值的时候的处理方式。
于是乎,通过这种有效值、无效值的枚举方式,实现了使用、判断等场景下的安全性。
这里提到了“判断”,接下来我们就要讲match表达式了。


2 match

match是一个控制流运算符。比如说——

enum Coin {Penny,Nickel,Dime,Quarter,
}fn value_in_cents(coin: Coin) -> u8 {match coin {Coin::Penny => {println!("Lucky penny!"); // 逻辑处理1 // 有返回值}Coin::Nickel => 5, // 如果代码较短,可以不用括号Coin::Dime => 10,Coin::Quarter => 25, // 必须穷尽所有的可能的处理}
}

是不是有点像c/cpp的switch语法?让我们问问chatgpt吧,他说:
1、强大的模式匹配:match支持强大的模式匹配包括结构体、枚举、引用等等;
2、更好的安全性:match必须处理所有可能的情况;
3、更好的表达能力:match可以返回值

好的,那让我们继续看看这些神奇的点吧
模式匹配,还有一个功能就是可以绑定匹配的模式的部分值,也就是可以从枚举成员中,提取出值来使用

#[derive(Debug)] // 这样可以立刻看到州的名称
enum UsState {Alabama,Alaska,// --snip--
}
enum Coin {Penny,Nickel,Dime,Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {match coin {Coin::Penny => 1,Coin::Nickel => 5,Coin::Dime => 10,Coin::Quarter(state) => { // 增加state变量 println!("State quarter from {:?}!", state);25} // 像这样,就可以获取coin的quarter成员中的,内部的值}
}

上面说的,都是enum,不要忘了我们的option哦,通过match模式的必须穷举处理的特性,避免了遗漏空值处理场景的编码问题

fn plus_one(x: Option<i32>) -> Option<i32> {match x {None => None, // 如果没有这一条,没有穷尽地匹配,编译器会报错Some(i) => Some(i + 1),}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);

另外,还有统配模式与_占位符可以使用,要注意的是,other意味着你需要使用这个变量值,而如果不需要变量值你可以使用_

let dice_roll = 9;
match dice_roll {3 => add_fancy_hat(),7 => remove_fancy_hat(),other => move_player(other), // 如果要使用变量值
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
let dice_roll = 9;
match dice_roll {3 => add_fancy_hat(),7 => remove_fancy_hat(),_ => reroll(), // 如果不需要使用变量值// _ => (), // 如果不需要处理
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn reroll() {}

3 if let

if let是更简单的控制流,没有什么好说的,直接看代码就行,比如以下两种方式,效果是相同的——

let mut count = 0;
match coin {Coin::Quarter(state) => println!("State quarter from {:?}!", state),_ => count += 1,
}
let mut count = 0;
if let Coin::Quarter(state) = coin {println!("State quarter from {:?}!", state);
} else {count += 1;
}

这篇关于【Rust】第七节:枚举与模式匹配的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/646891

相关文章

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Rust中的注释使用解读

《Rust中的注释使用解读》本文介绍了Rust中的行注释、块注释和文档注释的使用方法,通过示例展示了如何在实际代码中应用这些注释,以提高代码的可读性和可维护性... 目录Rust 中的注释使用指南1. 行注释示例:行注释2. 块注释示例:块注释3. 文档注释示例:文档注释4. 综合示例总结Rust 中的注释

Rust格式化输出方式总结

《Rust格式化输出方式总结》Rust提供了强大的格式化输出功能,通过std::fmt模块和相关的宏来实现,主要的输出宏包括println!和format!,它们支持多种格式化占位符,如{}、{:?}... 目录Rust格式化输出方式基本的格式化输出格式化占位符Format 特性总结Rust格式化输出方式

Rust中的Drop特性之解读自动化资源清理的魔法

《Rust中的Drop特性之解读自动化资源清理的魔法》Rust通过Drop特性实现了自动清理机制,确保资源在对象超出作用域时自动释放,避免了手动管理资源时可能出现的内存泄漏或双重释放问题,智能指针如B... 目录自动清理机制:Rust 的析构函数提前释放资源:std::mem::drop android的妙

Rust中的BoxT之堆上的数据与递归类型详解

《Rust中的BoxT之堆上的数据与递归类型详解》本文介绍了Rust中的BoxT类型,包括其在堆与栈之间的内存分配,性能优势,以及如何利用BoxT来实现递归类型和处理大小未知类型,通过BoxT,Rus... 目录1. Box<T> 的基础知识1.1 堆与栈的分工1.2 性能优势2.1 递归类型的问题2.2

在Rust中要用Struct和Enum组织数据的原因解析

《在Rust中要用Struct和Enum组织数据的原因解析》在Rust中,Struct和Enum是组织数据的核心工具,Struct用于将相关字段封装为单一实体,便于管理和扩展,Enum用于明确定义所有... 目录为什么在Rust中要用Struct和Enum组织数据?一、使用struct组织数据:将相关字段绑

浅析Rust多线程中如何安全的使用变量

《浅析Rust多线程中如何安全的使用变量》这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下... 目录1. 向线程传递变量2. 多线程共享变量引用3. 多线程中修改变量4. 总结在Rust语言中,一个既引人入胜又可

C#实现获得某个枚举的所有名称

《C#实现获得某个枚举的所有名称》这篇文章主要为大家详细介绍了C#如何实现获得某个枚举的所有名称,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考一下... C#中获得某个枚举的所有名称using System;using System.Collections.Generic;usi

Rust 数据类型详解

《Rust数据类型详解》本文介绍了Rust编程语言中的标量类型和复合类型,标量类型包括整数、浮点数、布尔和字符,而复合类型则包括元组和数组,标量类型用于表示单个值,具有不同的表示和范围,本文介绍的非... 目录一、标量类型(Scalar Types)1. 整数类型(Integer Types)1.1 整数字

Java 枚举的常用技巧汇总

《Java枚举的常用技巧汇总》在Java中,枚举类型是一种特殊的数据类型,允许定义一组固定的常量,默认情况下,toString方法返回枚举常量的名称,本文提供了一个完整的代码示例,展示了如何在Jav... 目录一、枚举的基本概念1. 什么是枚举?2. 基本枚举示例3. 枚举的优势二、枚举的高级用法1. 枚举