深入了解Rust中的枚举和模式匹配

 更新时间:2024年01月11日 09:41:49   作者:二次元攻城狮  
这篇文章主要为大家详细介绍了Rust中的枚举和模式匹配的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下

枚举的定义

结构体可以将字段和数据聚合在一起,而枚举可以将一个值成为一个集合之一。

定义一个 IpAddrKind 枚举:

enum IpAddrKind {
    V4,
    V6,
}

枚举值

创建 IpAddrKind 两个不同成员的实例:

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

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

定义一个函数来获取任何 IpAddrKind,可以使用任一成员来调用这个函数:

fn route(ip_kind: IpAddrKind) {}
route(IpAddrKind::V4);
route(IpAddrKind::V6);

将数据直接放进每一个枚举成员

将 IP 地址的数据和 IpAddrKind 成员存储在一个 struct 中,关联枚举成员与值:

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

可以使用一种更简洁的方式来表达相同的概念,仅仅使用枚举并将数据直接放进每一个枚举成员而不是将枚举作为结构体的一部分。

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

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

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

IpAddr::V4() 是一个获取 String 参数并返回 IpAddr 类型实例的函数调用,这些构造函数会自动被定义。

将不同类型和数量的数据放入枚举成员

用枚举替代结构体还有另一个优势:每个成员可以处理不同类型和数量的数据。枚举则可以轻易的处理这个情况:

enum IpAddr {
    V4(u8, u8, u8, u8),  //V4 地址存储为四个 u8 值
    V6(String),          //V6 地址存储为一个 String
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));

存储和编码 IP 地址实在是太常见了,标准库提供了一个开箱即用的定义:

struct Ipv4Addr {
    // --snip--
}

struct Ipv6Addr {
    // --snip--
}

enum IpAddr {
    V4(Ipv4Addr),
    V6(Ipv6Addr),
}

这说明可以将任意类型的数据放入枚举成员中:例如字符串、数字类型或者结构体,甚至可以包含另一个枚举。

有关联值的枚举和结构体的相似性

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

enum Message {
    Quit,                       //Quit 没有关联任何数据
    Move { x: i32, y: i32 },    //Move 类似结构体包含命名字段
    Write(String),              //Write 包含单独一个 String
    ChangeColor(i32, i32, i32), //ChangeColor 包含三个 i32
}

如下结构体可以包含和上面枚举成员相同的数据,但它们都有不同的类型:

struct QuitMessage; // 类单元结构体
struct MoveMessage {
    x: i32,
    y: i32,
}
struct WriteMessage(String); // 元组结构体
struct ChangeColorMessage(i32, i32, i32); // 元组结构体

使用 impl 在枚举上定义方法

可以使用 impl 在枚举上定义方法,在 Message 枚举上定义一个叫做 call 的方法:

impl Message {
    fn call(&self) {
        // 在这里定义方法体
    }
}

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

方法体使用了 self 来获取调用方法的值,上面的变量 m 就是当 m.call() 运行时 call 方法中的 self 的值。

Option 枚举和其相对于空值的优势

Option 是标准库定义的一个枚举,它编码了一个非常普遍的场景:一个值要么有值要么没值。

Rust 没有空值功能,空值(Null )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。

Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举 Option<T>,而且它定义于标准库中,如下:

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

Option 枚举包含在 prelude 之中不需要将其显式引入作用域,它的成员也可以不需要 Option:: 前缀来直接使用 Some 和 None。

一些包含数字类型和字符串类型 Option 值的例子:

//根据Some 成员的值推断变量类型
let some_number = Some(5); //some_number 的类型是 Option<i32>
let some_char = Some('e'); //some_char 的类型是 Option<char>
//需要显示指定 Option 整体的类型 为 Option<i32>
let absent_number: Option<i32> = None;

因为 Option 和 T(这里 T 可以是任何类型)是不同的类型,编译器不允许像一个肯定有效的值那样使用 Option:
代码尝试将 Option 与 i8 相加,无法通过编译:

let x: i8 = 5;
let y: Option<i8> = Some(5);
//错误,无法通过编译!
let sum = x + y;

在对 Option 进行运算之前必须将其转换为 T,这能帮助我们捕获到空值最常见的问题之一:假设某值不为空但实际上为空的情况。

为了使用 Option 值,需要编写处理每个成员的代码。match 表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据:

let some_value: Option<i32> = Some(42);
match some_value {
    Some(value) => {
        println!("The value is: {}", value);
        // 在这里可以使用 value
    }
    None => {
        println!("The value is None");
        // 处理 None 的情况
    }
}

match 控制流结构

Rust 有一个叫做 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,
    }
}

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

绑定值的模式

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

改变 Quarter 成员来包含一个 State 值:

#[derive(Debug)] // 这样可以立刻看到州的名称
enum UsState {
    Alabama,
    Alaska,
    // --snip--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

在匹配 Coin::Quarter 成员的分支的模式中增加了一个叫做 state 的变量,当匹配到 Coin::Quarter 时变量 state 将会绑定对应州的值:

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("State quarter from {:?}!", state);
            25
        }
    }
}

匹配 Option<T>

在 Option<i32> 上使用 match 表达式的函数:

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        //i 绑定了 Some 中包含的值
        Some(i) => Some(i + 1),
    }
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);

将 match 与枚举相结合在很多场景中都是有用的,Rust 代码中有很多这样的模式:match 一个枚举,绑定其中的值到一个变量,接着根据其值执行代码

匹配是穷尽的

以下代码没有处理 None 的情况,无法通过编译:

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        Some(i) => Some(i + 1),
    }
}

Rust 中的匹配是 穷尽的(exhaustive):必须穷举到最后的可能性来使代码有效。

通配模式和 _ 占位符

对一些特定的值采取特殊操作,而对其他的值采取默认操作,模式 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() {}

if let 简洁控制流

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

match 只关心当值为 Some 时执行代码:

let config_max = Some(3u8);
match config_max {
    Some(max) => println!("The maximum is configured to be {}", max),
    _ => (),
}

可以使用 if let 这种更短的方式编写:

let config_max = Some(3u8);
if let Some(max) = config_max {
    println!("The maximum is configured to be {}", max);
}

可以在 if let 中包含一个 else。else 块中的代码与 match 表达式中的 _ 分支块中的代码相同,这样的 match 表达式就等同于 if let 和 else。

使用 match 表达式:

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

使用 if let 和 else 表达式:

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

以上就是深入了解Rust中的枚举和模式匹配的详细内容,更多关于Rust枚举和模式匹配的资料请关注脚本之家其它相关文章!

相关文章

  • Rust中的关联类型总结

    Rust中的关联类型总结

    关联类型是定义通用trait的一种机制。它允许在trait中定义一个或多个占位符类型,这些类型将在trait的实现中具体化。文中有详细示例代码供参考,需要的朋友可以阅读一下
    2023-05-05
  • Rust实现一个表达式Parser小结

    Rust实现一个表达式Parser小结

    这篇文章主要为大家介绍了Rust实现一个表达式Parser小结,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Rust 枚举和模式匹配的实现

    Rust 枚举和模式匹配的实现

    枚举是 Rust 中非常重要的复合类型,也是最强大的复合类型之一,广泛用于属性配置、错误处理、分支流程、类型聚合等场景中,本文就来介绍一下Rust 枚举和模式匹配,感兴趣的可以了解一下
    2023-12-12
  • 详解Rust中的方法

    详解Rust中的方法

    方法其实就是结构体的成员函数,在C语言中的结构体是没有成员函数的,但是Rust毕竟也是一门面向对象的编程语言,所以给结构体加上方法的特性很符合面向对象的特点,这篇文章主要介绍了Rust中的方法,需要的朋友可以参考下
    2022-10-10
  • Rust读取配置文件的实现步骤

    Rust读取配置文件的实现步骤

    任何项目都离不开对于配置文件的读取和解析,rust项目也一样,本文主要介绍了Rust读取配置文件的实现步骤,具有一定的参考价值,感兴趣的可以了解一下
    2023-12-12
  • Rust 入门之函数和注释实例详解

    Rust 入门之函数和注释实例详解

    这篇文章主要为大家介绍了Rust 入门之函数和注释实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Rust duckdb和polars读csv文件比较情况

    Rust duckdb和polars读csv文件比较情况

    duckdb在数据分析上,有非常多不错的特质,1、快;2、客户体验好,特别是可以同时批量读csv在一个目录下的csv等文件,今天来比较下Rust duckdb和polars读csv文件比较的情况,感兴趣的朋友一起看看吧
    2024-06-06
  • Rust 连接 SQLite 数据库的过程解析

    Rust 连接 SQLite 数据库的过程解析

    本文通过一个例子给大家介绍了Rust 连接 SQLite 数据库的详细过程,我使用rusqlite这个crate,对Rust 连接 SQLite 数据库相关知识感兴趣的朋友跟随小编一起看看吧
    2022-01-01
  • 使用Cargo工具高效创建Rust项目

    使用Cargo工具高效创建Rust项目

    这篇文章主要介绍了使用Cargo工具高效创建Rust项目,本文有关Cargo工具的使用和Rust输入输出知识感兴趣的朋友一起看看吧
    2022-08-08
  • Rust使用Channel实现跨线程传递数据

    Rust使用Channel实现跨线程传递数据

    消息传递是一种很流行且能保证安全并发的技术,Rust也提供了一种基于消息传递的并发方式,在rust里使用标准库提供的Channel来实现,下面我们就来学习一下如何使用Channel实现跨线程传递数据吧
    2023-12-12

最新评论