Skip to main content
5 枚举与模式匹配

5 枚举与模式匹配

•1 min•153 words

枚举

Rust中的枚举并非简单

可枚举的“值”,这些“值”可以指以相同tag开头的所有值。比如 1-100 与 1-876 都属于 枚举值“1”,都属于同一个枚举结构体。

枚举的本质

枚举的概念和意义

一个枚举值,表示一此值可能属于很多种类型。将这些类型的数据都可以兼容到专门设计的内存结构中。稍后再做具体类型处理。

Summary

在定义枚举的时候,我们需要定义枚举中有哪些“枚举变体”,这些枚举变体的数据类型。

rust会给每个枚举变体一个标签值,用于区分不同的枚举变体。

然后将所有枚举变体的内存进行计算得到能容纳所有种类的枚举变体的内存大小。

当我们创建一个枚举变体的实例的时候,分配的内存中,首先是标签,然后是枚举的数据区,rust能通过标签判断实例所属的枚举变体,并了解后面内存存储的数据类型然后通过模式匹配能取出数据。

    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }

    let home = IpAddr::V4(127, 0, 0, 1);
    let iphone = IpAddr::V4(192, 168, 0, 1);

    let loopback = IpAddr::V6(String::from("::1"));
类型枚举的本质

实际上可以意识到,枚举结构体定义了一种可以兼容有限个数的类型结构。就像我们把好几个类型拉过来组成了一个枚举。
但是rust让内部类型通过标签来表示,在外部隐藏了内部的类型,让整体都属于枚举结构体的样子,我们通过type_name_of_val只能获取到枚举结构体的名字。

常用的枚举变体

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

枚举像结构体一样定义方法

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

如何获取实例中数据

内部数据需要通过模式匹配获取

因为内部数据类型被枚举结构体掩盖,所以要获取内部数据需要内部数据的实际类型。
rust设计了解析枚举tag的途径,现在去后面的模式匹配寻找答案。

Option枚举

前言

在非常多的编程语言中我们都看到过Option对空引用的处理。将所有可能为空会导致空引用的地方使用Option类型,从而通过语法来约束我们要处理空引用的情况。

从任何引用都有可能为空,到任何引用都不可能为空

原来的C/C++等,每个引用类型都可以使用空值传入,而rust中null值不属于引用类型,所以没有空的概念。
避免了我们必须每时每刻需要判断一个引用是否为空。

Option在rust中表示可能为空的type

空的概念是存在的,但是在默认不为空的引用中,我们用Option枚举表示这个值有None的可能。当我们获得一个Option类型的值,就应该知道我们要判断是一个None吗。

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

option还有很多方法,可以使用。

模式匹配

Summary
  • 按变量的值来划分成1,3,5,7和other值。
  • 按变量的内部类型(枚举的内部类型)来划分,看内部标签类型是”ipv4”还是”ipv6”
  • 按变量的内存结构划分,{x:i32, y:i32} 能匹配同类型的值{ x:32, y:12 },(String,i32) 匹配 (String::from(“hello”),32) 。如果结构不同那么不会匹配。
  • 联合多种划分嵌套起来:
    • 比如枚举内部类型匹配+内部类型的值内容划分
模式匹配中不可以使用变量,变量变成了容器

如果可以使用变量,那么有限可能的模式就可能出现纰漏,如果想实现动态的分支,可以使用函数。

模式匹配分支

模式匹配可以成功也可以失败

一般来说,我们的模式匹配都只能书写成功的分支,不适配的模式会被编译器检查出错误。
实际上rust提供了对模式匹配结果的成功与失败两种分支。当我们使用if let或者while let 等就能对模式匹配的成功或失败进行控制流分支操作。

常用的模式匹配分支语法是if let <pattern> = expression { 语法块 }

while let <pattern> = expression { 代码块 } 语法与if let语法相似,只有当模式匹配的时候才会执行循环体。

模式匹配的应用

Summary
  • 变量的声明
let (x:i32,y:u8) = (-12, 9);
  • 函数参数
    类似这样:fn funcionName(<pattern> : <type>)
fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({x}, {y})");
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}

模式语法

Summary
  • 按变量的值进行匹配
  • 按变量的结构进行匹配
  • 按枚举变量的内部结构进行匹配
  • 嵌套前面多种进行匹配
	// --------------------------按变量的值进行匹配-----------------
    let x = 1;

    match x {
        1 => println!("one"),
        2 => println!("two"),
        3 => println!("three"),
        _ => println!("anything"),
    }
    // ------------------- 按变量的结构进行匹配----------------------
    struct Point{x:i32,y:i32};
    let x = Point{12,32};
    match x {
        Point { x, y: 0 } => println!("On the x axis at {x}"),
        Point { x: 0, y } => println!("On the y axis at {y}"),
        Point { x, y } => {
            println!("On neither axis: ({x}, {y})");
        }
    }
    // --------------------按枚举变量的内部结构进行匹配-------------------
	enum Message {
	    Quit,
	    Move { x: i32, y: i32 },
	    Write(String),
	    ChangeColor(i32, i32, i32),
	}
	let x = IpAdressKind::Ipv4(192.169.0.1);
	match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.");
        }
        Message::Move { x, y } => {
            println!("Move in the x direction {x} and in the y direction {y}");
        }
        Message::Write(text) => {
            println!("Text message: {text}");
        }
        Message::ChangeColor(r, g, b) => {
            println!("Change color to red {r}, green {g}, and blue {b}");
        }
    }
    // ------------------------嵌套前面多种----------------------------------
    enum Color { Rgb(i32, i32, i32), Hsv(i32, i32, i32), }
    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));

    match msg {
        Message::ChangeColor(Color::Rgb(r, g, b)) => {
            println!("Change color to red {r}, green {g}, and blue {b}");
        }
        Message::ChangeColor(Color::Hsv(h, s, v)) => {
            println!("Change color to hue {h}, saturation {s}, value {v}");
        }
        _ => (),
    }
Summary
  • ..语法是用于生成连续列表的,可以用于匹配可数值范围1..=5就是1到5,1..5就是1到4
  • |符号可以用于连接多个模式,只要匹配任意一个模式则此连接模式匹配成功1|2
  • _符号可用于表示忽略,在变量这篇讲过_开头的变量用于让编译器忽略检查变量是否unused,在模式匹配中也可以表示变量并忽略值(功能类似变量,但并不会创建变量)。
  • ..还可以表示结构剩余部分的忽略,但是只能用在无歧义的地方。

高级语法

Summary
  • 匹配守卫用在 match 分支模式之后的额外 if 条件,在这个区间模式已匹配可以使用匹配变量中的值,从而更方便的对变量的值进行判断。
  • @绑定则是对内部字段绑定变量,并对变量的值进行判断。
	// 匹配守卫
    let num = Some(4);

    match num {
        Some(x) if x % 2 == 0 => println!("The number {x} is even"),
        Some(x) => println!("The number {x} is odd"),
        None => (),
    }
	// @绑定:接在对应的变量后面,对变量的值进行额外的确认
    enum Message {
        Hello { id: i32 },
    }

    let msg = Message::Hello { id: 5 };

    match msg {
        Message::Hello {
            id: id_variable @ 3..=7,
        } => {
            println!("Found an id in range: {id_variable}")
        }
        Message::Hello { id: 10..=12 } => {
            println!("Found an id in another range")
        }
        Message::Hello { id } => println!("Found some other id: {id}"),
    }
Todo
let arr = [1, 2, 3, 4];
match arr {
   [first, tail @ ..] => {
       // tail 的类型是 &[i32] 或 [i32; 3] 取决于 arr 的类型
       println!("first: {}, tail: {:?}", first, tail);
   }
}

match控制流

match控制流是一个模式匹配分支,并且是多分支

一个模式匹配的分支只有匹配成功和匹配失败两分支,而match控制流通过提供多模式,来让变量匹配更多的分支,只有当前面的模式匹配失败才会进入后面的模式匹配。

if let与let else控制流

Summary
let else能将变量绑定在外部作用域

let 模式 = 表达式 else { 代码块 }
这个语法的好处就是可以将变量直接绑定到语句所在的作用域,而不是内部的作用域。
可以将let else 视做let语法的变种,let是只模式匹配成功的,而let else则是成功就失败都处理。