写点什么

30 天拿下 Rust 之枚举

作者:希望睿智
  • 2024-05-25
    安徽
  • 本文字数:3611 字

    阅读完需:约 12 分钟

概述

Rust 中的枚举是一种用户定义的类型,它允许你为一组相关的值赋予友好的名称。在 Rust 中,枚举是强大的工具,它们不仅仅用于表示几个固定的值,还可以包含函数和方法,使得枚举成员可以有自己的行为。通过与模式匹配和其他 Rust 特性结合使用,枚举在构建健壮、无崩溃的应用程序中发挥了重要作用,并可大幅提高代码的可读性、可维护性和类型安全性。

基础枚举

在 Rust 中,枚举通过关键字 enum 进行声明,它可以包含一组相关的命名常量。比如:我们可以定义一个枚举来表示一周的几天。

enum Day {    Monday,    Tuesday,    Wednesday,    Thursday,    Friday,    Saturday,    Sunday,}
复制代码

定义好枚举后,我们可以像下面这样使用枚举值。

let cur_day = Day::Wednesday;
复制代码

关联枚举

Rust 中的枚举还可以带有关联值,这使得枚举成员可以有不同的数据类型。比如:我们可以定义一个表示结果的枚举,其中一个成员包含整数值,另一个成员则包含字符串值。

enum Result {    Ok(i32),    Err(String),}
fn main() { let success = Result::Ok(66); let failure = Result::Err(String::from("failed"));}
复制代码

在上面的示例代码中,Result::Ok 有一个 i32 类型的关联值,Result::Err 有一个 String 类型的关联值。

另外,我们还可以为枚举中的属性命名,类似于结构体的语法。但请特别注意:枚举并不能像访问结构体字段那样访问枚举绑定的属性,访问的方法可参考下面的匹配枚举。

enum Shape {    Point {x: i32, y: i32},    Rectangle {width: i32, height: i32},    Circle(i32),}
fn main() { let point = Shape::Point{x: 66, y: 88}; let rect = Shape::Rectangle{width: 10, height: 20}; let circle = Shape::Circle(100);}
复制代码

匹配枚举

使用 match 表达式,可以很方便地处理枚举类型的值。Rust 强制要求枚举的所有可能变体在 match 表达式中都被考虑到,以避免未处理的枚举导致的运行时错误。

enum Direction {    Up(u32),    Down(i32),    Left(String),    Right(String),}
fn main() { let direction = Direction::Up(66); match direction { Direction::Up(value) => println!("turn up by {}", value), Direction::Down(value) => println!("turn down by {}", value), Direction::Left(text) => println!("turn left to {}", text), Direction::Right(text) => println!("turn right to {}", text), }}
复制代码

match 表达式也可以当作函数表达式来对待,它是可以有返回值的。但有一点需要注意:所有返回值表达式的类型必须一样。

enum Direction {    Up(u32),    Down(i32),    Left(String),    Right(String),}
fn convert(direction: Direction) -> u32 { match direction { Direction::Up(value) => 100, Direction::Down(value) => 200, Direction::Left(text) => 300, Direction::Right(text) => 400, }}
fn main() { let value = convert(Direction::Down(99)); println!("{}", value);}
复制代码

在 match 表达式中,还可以使用通配符,用于对一些特定的值采取特殊操作,而对其他的值采取默认操作。在下面的示例代码中,我们对 88 和 99 采取了特殊操作,但对其他值采取了统一的默认处理。

fn main() {    let value = 66;    match value {        88 => println!("conditon 88"),        99 => println!("conditon 99"),        other => println!("other conditon {}", other),    }}
复制代码

注意:我们必须将通配分支放在最后,因为模式是按顺序匹配的。如果我们在通配分支后再添加其他分支,Rust 在编译时会警告我们“unreachable pattern”,因为此后的分支永远不会被匹配到。

另外,Rust 还提供了一种模式:当我们不想使用通配模式获取的值时,可以使用占位符_。这是一种特殊的模式,可以匹配任意值而不绑定到该值。占位符会告诉 Rust,我们不会使用这个值,因此 Rust 也不会警告我们存在未使用的变量。

fn main() {    let value = 66;    match value {        88 => println!("conditon 88"),        99 => println!("conditon 99"),        _ => println!("other conditons"),    }}
复制代码

可以看到,对于只有两种匹配情况的场景来说,match 显得比较繁琐,必须使用通配符或占位符。为此,Rust 提供了语法糖 if let,用于简化代码。可以在 if let 中包含一个 else,else 块中的代码与 match 表达式中占位符分支块中的代码相同。

fn main() {    let value = 66;    if let 66 = value {        println!("conditon 66");    } else {        println!("other conditons");    }}
复制代码

使用 if let,意味着编写更少的代码,但这会失去 match 强制要求的穷尽性检查(因为 else 是可选的)。到底该使用 match 还是 if let,取决于我们对增加简洁度和失去穷尽性检查之间的权衡取舍。

Option 枚举

Rust 中的 Option 类型是一种枚举,它是 Rust 语言的核心特性之一,用于处理值可能存在的状态。在许多其他编程语言中,这种场景可能会使用 null、None 或其他表示空或缺失的特殊值来处理,但这些通常会导致潜在的空引用错误。而 Rust 通过设计 Option<T>类型,强制开发者在编译时就必须考虑值可能不存在的情况,从而保证了运行时的安全性。

Option 类型的定义如下:

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

Option<T>中,T 是一个泛型参数,代表了当值存在时的具体类型。这意味着,Option 可以包裹任何类型的值。比如:Option<i32>表示可能包含一个整数值,或者没有值。

Option 类型提供的主要方法如下。

  • unwrap(): 如果 Option 是 Some(value),则返回该 value;如果 Option 是 None,则触发异常。这主要用于开发阶段调试和确定程序逻辑正确的地方,不推荐在生产代码中滥用,因为它会直接终止程序执行。

  • expect(msg): 类似于 unwrap(), 但在触发异常时,提供了一个自定义的消息。

  • is_none(): 返回一个布尔值,表示 Option 是否为 None。

  • is_some(): 返回一个布尔值,表示 Option 是否有值。

  • ok_or(err): 将 Option 转换成 Result,若为 Some 则映射到 Ok(_),若为 None 则映射到 Err(err)。

  • map(f): 如果 Option 是 Some(T),应用函数 f 给 T 并返回一个新的 Option<T'>(T'是 f 作用后的新类型)。如果 Option 是 None,则返回 None。

  • and_then(f): 类似于 map(),但是 f 必须返回一个 Option<T'>,它将链式调用并保持 Option 的状态。

  • unwrap_or(default): 如果 Option 是 Some,则返回其中的值。否则,返回提供的默认值。

  • unwrap_or_else(f): 类似于 unwrap_or(),但当 Option 为 None 时,调用函数 f 生成默认值。

Option 类型的具体使用方法,可参考下面的示例代码。

fn divide(a: i32, b: i32) -> Option<i32> {    if b == 0 {        None    } else {        Some(a / b)    }}
fn main() { let result = divide(10, 2); match result { Some(value) => println!("result is: {}", value), None => println!("can't be zero"), }
// 当除数非零时,得到结果;否则,返回-1。 let value = divide(10, 0).unwrap_or(-1); println!("value is: {}", value);
// 使用map进行链式操作,输出: Some(10) let number = Some(5); let number2 = number.map(|n| n * 2); println!("number2 is: {:?}", number2);}
复制代码

Option 类型在 Rust 中有着广泛的应用场景,可以用于初始化值、作为函数的返回值、表示简单错误、作为结构体的可选字段等。通过使用 Option,我们可以更加明确地处理可能为空的情况,从而避免许多由于空值引起的运行时错误。这也是 Rust 语言相对于 C/C++等语言的一大明显优势。

枚举绑定方法

与结构体类似,Rust 的枚举还允许你在枚举成员上定义函数和方法。比如:我们可以给上面的 Result 枚举添加一个 describe 方法。

enum Result {    Ok(i32),    Err(String),}
impl Result { fn describe(&self) -> &str { match self { Result::Ok(_) => "Operation was successful", Result::Err(_) => "Operation failed", } }}
fn main() { let success = Result::Ok(42); let failure = Result::Err(String::from("something went wrong"));
println!("{}", success.describe()); println!("{}", failure.describe()); }
复制代码

总结

Rust 的枚举提供了一种安全且灵活的方式来处理多种可能的状态和值,使用枚举的优点主要有以下几点。

1、代码清晰性:使用枚举可以使代码更具可读性和可维护性,因为它们为可能的值提供了明确的名称。

2、类型安全:枚举是强类型的,这意味着不能将错误的类型分配给枚举值。

3、灵活性:枚举可以包含关联值,这使得它们能够表示更复杂的数据结构。

4、扩展性:可以在任何时候向枚举添加新的成员,而不会破坏现有的代码。

总之,理解和熟练运用枚举,能够使我们在 Rust 编程过程中设计出更为简洁、优雅的代码结构。

发布于: 刚刚阅读数: 4
用户头像

希望睿智

关注

一起学习,一起成长,一起进步! 2024-05-21 加入

中国科学技术大学毕业,在客户端、运营级平台、Web开发、嵌入式开发、深度学习、人工智能、音视频编解码、图像处理、流媒体等多个领域具备实战开发经验和技术积累,共发表发明专利十余项,软件著作权几十项。

评论

发布
暂无评论
30天拿下Rust之枚举_rust语言_希望睿智_InfoQ写作社区