在这里插入图片描述

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

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

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

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

在这里插入图片描述

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语言的定义方式

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提倡的方式定义枚举,可以直接在定义枚举字段的时候,定义好字段类型

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

//枚举值可以是不同类型
#[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);

在这里插入图片描述

枚举的经典用法
枚举的成员中内嵌了多种多样的类型

enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}

一个 Message 枚举,其每个成员都储存了不同数量和类型的值
这个枚举有四个含有不同类型的成员:
Quit 没有关联任何数据。
Move 包含一个匿名结构体
Write 包含单独一个 String 。
ChangeColor 包含三个 i32 。

枚举类似于定义不同类型的结构体,除了枚举不使用 struct 关键字并且所有成员都被组合在一起位于 Message 下之外。
如下这些结构体可以包含与之前枚举成员中相同的数据:

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 的方法:

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 基本语法

match value {
    pattern1 => expression1,
    pattern2 => expression2,
    // ...
    _ => default_expression,
}

match后面跟要匹配的表达式,穷举模式的各种pattern。=> 运算符将模式和将要运行的代码分开。每一个分支之间使用逗号分隔
当 match 表达式执行时,它将结果值按顺序与每一个分支的模式相比较,如果模式匹配了这个值,这个模式相关联的代码将被执行,后面的模式就不再去匹配。
如果模式并不匹配这个值,将继续执行下一个分支。
每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个 match 表达式的返回值。

2.2 基本示例

let number = 3;

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

2.3 匹配枚举

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

案例一:

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();

在这里插入图片描述

案例二:

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 绑定值

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

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 支持多种模式匹配:

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 范围匹配

let age = 25;

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

在这里插入图片描述

2.7 守卫条件

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

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值,所以 _ 的情况什么也不会发生。
因此,可以说我们想要对 _ 通配符之前没有列出的所有可能的值不做任何处理。
前面所有的模式都匹配补上,走 _ 这个通配符模式

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 基本定义

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

T表示泛型的类型
Rust中的空一般会结合Option这个枚举类型来表示,变愉快控制程序的bug
Rust 中的匹配是 穷尽的(exhaustive):必须穷举
到最后的可能性来使代码有效。特别的在这个 Option 的例子中,Rust 防止我们忘记明确的处理 None 的情况,
这使我们免于假设拥有一个实际上为空的值,这造成了价值亿万的错误。

基本匹配模式

3.1 简单匹配

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 中的值

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

//处理函数返回的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 处理

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中值为多少,都会匹配到

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 提供默认值

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,用来判断

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

在这里插入图片描述

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐