【Rust】Rust中的枚举与模式匹配,原理解析与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑

🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。

🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏: Rust语言通关之路
景天的主页: 景天科技苑

文章目录

  • Rust枚举与模式匹配
    • 1、定义枚举
      • [1.1 类似C语言的定义方式](#1.1 类似C语言的定义方式)
      • [1.2 Rust语言提倡的方式](#1.2 Rust语言提倡的方式)
    • [2、match 控制流运算符](#2、match 控制流运算符)
      • [2.1 基本语法](#2.1 基本语法)
      • [2.2 基本示例](#2.2 基本示例)
      • [2.3 匹配枚举](#2.3 匹配枚举)
      • [2.4 绑定值](#2.4 绑定值)
      • [2.5 模式匹配](#2.5 模式匹配)
      • [2.6 范围匹配](#2.6 范围匹配)
      • [2.7 守卫条件](#2.7 守卫条件)
      • [2.8 _ 通配符](#2.8 _ 通配符)
    • 3、Option
      • [3.1 简单匹配](#3.1 简单匹配)
      • [3.2 解构 Some 中的值](#3.2 解构 Some 中的值)
      • [3.3 处理函数返回的Option](#3.3 处理函数返回的Option)
      • [3.4 多层嵌套的 Option 处理](#3.4 多层嵌套的 Option 处理)
      • [3.5 忽略 Some 中的值](#3.5 忽略 Some 中的值)
      • [3.6 提供默认值](#3.6 提供默认值)
    • [4、if let 简单控制流](#4、if let 简单控制流)

Rust枚举与模式匹配

枚举(enumerations),也被称作 enums。枚举允许你通过列举可能的值来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。接下来,我们会探索一个特别有用的枚举,叫做 Option ,它代表一个值要么是某个值,要么什么都不是。然后会讲到在 match 表达式中用模式匹配,针对不同的枚举值编写相应要执行的代码。

最后会涉及到 if let ,另一个简洁方便处理代码中枚举的结构。

枚举是一个很多语言都有的功能,不过不同语言中其功能各不相同。

Rust 的枚举与 F#、OCaml 和 Haskell 这样的函数式编程语言中的 代数数据类型(algebraic data types)最为相似。

1、定义枚举

让我们看看一个需要诉诸于代码的场景,来考虑为何此时使用枚举更为合适且实用。

假设我们要处理 IP 地址。目前被广泛使用的两个主要 IP 标准:IPv4(version four)和 IPv6(version six)。

这是我们的程序可能会遇到的所有可能的 IP 地址类型:所以可以 枚举 出所有可能的值,这也正是此枚举名字的由来。

任何一个 IP 地址要么是 IPv4 的要么是 IPv6 的,而且不能两者都是。IP 地址的这个特性使得枚举数据结构非常适合这个

场景,因为枚举值只可能是其中一个成员。IPv4 和 IPv6 从根本上讲仍是 IP 地址,所以当代码在处理适用于任何类型的

IP 地址的场景时应该把它们当作相同的类型。

定义枚举语法: enum 枚举名称 { 字段 }

1.1 类似C语言的定义方式

rust 复制代码
fn main() {
    //定义枚举,加上debug用来打印枚举值
    #[derive(Debug)]
    enum IpAddrKind {
        V4,
        V6,
    }
    //定义结构体,使用枚举
    #[derive(Debug)]
    struct NewIpAddr {
        kind: IpAddrKind,
        address: String,
    }
    //实例化结构体
    let p1 = NewIpAddr {
        //使用枚举,枚举名::枚举字段
        kind: IpAddrKind::V4,
        address: String::from("10.10.0.10"),
    };
    let p2 = NewIpAddr {
        kind: IpAddrKind::V6,
        address: String::from("::1"),
    };

    //打印结构体
    println!("p1: {:?}", p1);
    println!("p2: {:?}", p2);
}

1.2 Rust语言提倡的方式

上面C语言方式定义枚举,枚举字段没定义类型。Rust提倡的方式定义枚举,可以直接在定义枚举字段的时候,定义好字段类型

rust 复制代码
fn main() {
    //Rust提倡的定义枚举方式
    #[derive(Debug)]
    enum IpAddr {
        V4(String),
        V6(String),
    }
    
    //实例化枚举
    let p1 = IpAddr::V4(String::from("127.0.0.1"));
    let p2 = IpAddr::V6(String::from("::1"));
    //打印枚举
    println!("p1: {:?}", p1);
    println!("p2: {:?}", p2);
}

IpAddr 枚举的新定义表明了 V4 和 V6 成员都关联了 String 值

我们直接将数据附加到枚举的每个成员上,这样就不需要一个额外的结构体了。

枚举值也可以是不同类型

V4 地址储存为四个 u8 值而 V6 地址仍然表现为一个 String

rust 复制代码
//枚举值可以是不同类型
#[derive(Debug)]
enum IpAddrKind {
    V4(u8, u8, u8, u8),
    V6(String),
}
//实例化枚举
let p3 = IpAddrKind::V4(127, 0, 0, 1);
let p4 = IpAddrKind::V6(String::from("::1"));
//打印枚举
println!("p3: {:?}", p3);
println!("p4: {:?}", p4);

枚举的经典用法

枚举的成员中内嵌了多种多样的类型

rust 复制代码
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

一个 Message 枚举,其每个成员都储存了不同数量和类型的值

这个枚举有四个含有不同类型的成员:

Quit 没有关联任何数据。

Move 包含一个匿名结构体

Write 包含单独一个 String 。

ChangeColor 包含三个 i32 。

枚举类似于定义不同类型的结构体,除了枚举不使用 struct 关键字并且所有成员都被组合在一起位于 Message 下之外。

如下这些结构体可以包含与之前枚举成员中相同的数据:

rust 复制代码
struct QuitMessage; // unit struct
struct MoveMessage {
    x: i32,
    y: i32,
}
struct WriteMessage(String); // tuple struct
struct ChangeColorMessage(i32, i32, i32); // tuple struct

不过如果我们使用不同的结构体,它们都有不同的类型,将不能轻易的定义一个获取任何这些信息类型的函数

结构体和枚举还有另一个相似点:就像可以使用 impl 来为结构体定义方法那样,也可以在枚举上定义方法。

这是一个定义于我们 Message 枚举上的叫做 call 的方法:

rust 复制代码
enum Message {
    Quit,
    Move {
        x: i32,
        y: i32,
    },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    fn call(&self) {
        // method body would be defined here
        println!("call");
    }
}
let m = Message::Write(String::from("hello"));
m.call();

可以实例化枚举中的一个或多个值,调用枚举的方法

方法体使用了 self 来获取调用方法的值。这个例子中,创建了一个拥有类型 Message::Write("hello") 的变量 m ,而且这就是当 m.call() 运行时 call 方法。

2、match 控制流运算符

Rust 有一个叫做 match 的极为强大的控制流运算符,match 类似于其他语言中的 switch 语句,但更加强大和安全。

它允许我们将一个值与一系列的模式相比较并根据相匹配的模式执行相应代码。

模式可由字面值、变量、通配符和许多其他内容构成;

match 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。

2.1 基本语法

rust 复制代码
match value {
    pattern1 => expression1,
    pattern2 => expression2,
    // ...
    _ => default_expression,
}

match后面跟要匹配的表达式,穷举模式的各种pattern。=> 运算符将模式和将要运行的代码分开。每一个分支之间使用逗号分隔

当 match 表达式执行时,它将结果值按顺序与每一个分支的模式相比较,如果模式匹配了这个值,这个模式相关联的代码将被执行,后面的模式就不再去匹配。

如果模式并不匹配这个值,将继续执行下一个分支。

每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 match 表达式的返回值。

2.2 基本示例

rust 复制代码
let number = 3;

match number {
    1 => println!("One"),
    2 => println!("Two"),
    3 => println!("Three"),  // 这里会匹配
    _ => println!("Other"),  // 通配模式,匹配所有其他情况
}

2.3 匹配枚举

match 在处理枚举时特别有用:

案例一:

rust 复制代码
enum Message {
    Quit,
    Move {
        x: i32,
        y: i32,
    },
    Write(String),
    ChangeColor(i32, i32, i32),
}

impl Message {
    //枚举的方法
    fn call(&self) {
        // method body would be defined here
        // * 解引用。match类似switch
        match *self {
            Message::Quit => println!("Quit"),
            Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y),
            Message::Write(ref text) => println!("Text message: {}", text),
            Message::ChangeColor(r, g, b) =>
                println!("Change color to r: {}, g: {}, b: {}", r, g, b),
        }
    }
}

let quit = Message::Quit;
quit.call();

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

let mymove = Message::Move { x: 10, y: 20 };
mymove.call();

let change_color = Message::ChangeColor(255, 0, 0);
change_color.call();

案例二:

rust 复制代码
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

let dir = Direction::Left;

match dir {
    Direction::Up => println!("Going up"),
    Direction::Down => println!("Going down"),
    Direction::Left => println!("Going left"),
    Direction::Right => println!("Going right"),
}

2.4 绑定值

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

可以在匹配时将值绑定到变量:

rust 复制代码
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
}

let msg = Message::Move { x: 10, y: 20 };

match msg {
    Message::Quit => println!("Quit"),
    Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y),
    Message::Write(text) => println!("Text message: {}", text),
}

2.5 模式匹配

Rust 的 match 支持多种模式匹配:

rust 复制代码
let pair = (0, -2);

match pair {
    (0, y) => println!("First is 0, y is {}", y),
    (x, 0) => println!("x is {}, second is 0", x),
    _ => println!("Other"),
}

2.6 范围匹配

rust 复制代码
let age = 25;

match age {
    0..=12 => println!("Child"),
    13..=19 => println!("Teenager"),
    20..=64 => println!("Adult"),
    _ => println!("Senior"),
}

2.7 守卫条件

可以在模式后添加 if 条件:

rust 复制代码
let num = Some(4);

match num {
    Some(x) if x < 5 => println!("Less than five: {}", x),
    Some(x) => println!("{}", x),
    None => (),
}

2.8 _ 通配符

_ 通配符类似switch里面的default。

Rust 也提供了一个模式用于不想列举出所有可能值的场景。例如, u8 可以拥有 0 到 255 的有效的值,如果我们只关心

1、3、5 和 7 这几个值,就并不想必须列出 0、2、4、6、8、9 一直到 255 的值。所幸我们不必这么做:可以使用特殊的模式 _ 替代

_ 模式会匹配所有的值。通过将其放置于其他分支之后, _ 将会匹配所有之前没有指定的可能的值。 () 就是 unit值,所以 _ 的情况什么也不会发生。

因此,可以说我们想要对 _ 通配符之前没有列出的所有可能的值不做任何处理。

前面所有的模式都匹配补上,走 _ 这个通配符模式

rust 复制代码
let some_u8_value = 0u8;
match some_u8_value {
    1 => println!("one"),
    3 => println!("three"),
    5 => println!("five"),
    7 => println!("seven"),
    _ => (),
}

3、Option

Option 是 Rust 标准库中定义的一个非常重要的枚举类型,用于表示一个值可能存在(Some)或不存在(None)。

Option 要么返回的是Some(),要么返回的是None

使用 match 来处理 Option 是 Rust 中常见的模式。

Option 基本定义

rust 复制代码
enum Option<T> {
    Some(T),
    None,
}

T表示泛型的类型

Rust中的空一般会结合Option这个枚举类型来表示,变愉快控制程序的bug

Rust 中的匹配是 穷尽的(exhaustive):必须穷举

到最后的可能性来使代码有效。特别的在这个 Option 的例子中,Rust 防止我们忘记明确的处理 None 的情况,

这使我们免于假设拥有一个实际上为空的值,这造成了价值亿万的错误。

基本匹配模式

3.1 简单匹配

rust 复制代码
let some_number = Some(5);  //这里的5,系统可以自动推导泛型类型为i32
let none_number: Option<i32> = None;

//模式匹配
match some_number {
    Some(i) => println!("Got a number: {}", i),
    None => println!("Got nothing"),
}

match none_number {
    Some(i) => println!("Got a number: {}", i),
    None => println!("Got nothing"),  // 这里会匹配
}

注意:以下两个数是不同的类型,x是i32类型,y是Option枚举类型

let x: i32 =5;

let y: Option = Some(5);

3.2 解构 Some 中的值

rust 复制代码
let some_string = Some("hello".to_string());

match some_string {
    Some(s) => println!("Got a string: {}", s),
    None => println!("Got no string"),
}

3.3 处理函数返回的Option

rust 复制代码
//处理函数返回的Option
fn divide(numerator: f64, denominator: f64) -> Option<f64> {
    if denominator == 0.0 { None } else { Some(numerator / denominator) }
}

let result = divide(10.0, 2.0);

match result {
    Some(x) => println!("Result: {}", x),
    None => println!("Cannot divide by zero"),
}

3.4 多层嵌套的 Option 处理

rust 复制代码
let nested_option = Some(Some(42));

match nested_option {
    Some(Some(value)) => println!("Double wrapped value: {}", value),
    Some(None) => println!("Inner is None"),
    None => println!("Outer is None"),
}

3.5 忽略 Some 中的值

some(_), 不管some中值为多少,都会匹配到

rust 复制代码
let option_val = Some(7);

match option_val {
    Some(_) => println!("Got something but don't care about the value"),
    None => println!("Got nothing"),
}

3.6 提供默认值

rust 复制代码
let maybe_number: Option<i32> = None;
let number = match maybe_number {
    Some(n) => n,
    None => 0,  // 默认值
};
println!("Number is: {}", number);

4、if let 简单控制流

虽然 match 是最全面的处理方式,但是我们有时候不想处理匹配不到的其他值或None,这是就可以使用if let 这种更短的方式编写

if let 语法让我们以一种不那么冗长的方式结合 if 和 let ,来处理只匹配一个模式的值而忽略其他模式的情况。

可以认为 if let 是 match 的一个语法糖,它当值匹配某一模式时执行代码而忽略所有其他值。

用法: if let Some(v) = 需要判断的Option

可以使用多个if let,用来判断

rust 复制代码
//if let语法
let some_u8_value = Some(3u8);
if let Some(2) = some_u8_value {
    println!("three");
} else if let Some(3) = some_u8_value {
    println!("three");
} else if let Some(4) = some_u8_value {
    println!("four");
} else {
    println!("other");
}
相关推荐
码起来呗2 分钟前
基于SpringBoot的高校学习讲座预约系统-项目分享
spring boot·后端·学习
坐吃山猪4 分钟前
Python-Agent调用多个Server-FastAPI版本
开发语言·python·fastapi
88号技师6 分钟前
【1区SCI】Fusion entropy融合熵,多尺度,复合多尺度、时移多尺度、层次 + 故障识别、诊断-matlab代码
开发语言·机器学习·matlab·时序分析·故障诊断·信息熵·特征提取
Asthenia04128 分钟前
Reactor 模型详解:从单线程到多线程及其在 Netty 和 Redis 中的应用
后端
北漂老男孩21 分钟前
Java对象转换的多种实现方式
java·开发语言
未来可期LJ33 分钟前
【Test】单例模式❗
开发语言·c++
Arenaschi43 分钟前
SQLite 是什么?
开发语言·网络·python·网络协议·tcp/ip
听雨·眠1 小时前
go语言中defer使用指南
开发语言·后端·golang
Yhame.1 小时前
【使用层次序列构建二叉树(数据结构C)】
c语言·开发语言·数据结构
言之。1 小时前
【Go语言】RPC 使用指南(初学者版)
开发语言·rpc·golang