Rust 枚举 简单入门

在计算机领域,总和类型(sum type)长期悲剧性缺位,很多事情却依然行得通,这简直不可思议(参见 Lambda 的缺位)。

------Graydon Hoare

Lambda 在主流编程领域的长期缺位造就了大量"烂"代码,总和类型的缺位同样如此

长期以来被 ML 社区和 Haskell 社区的黑客们称为总和类型、可区分的联合体(union)或代数数据类型。在 Rust 中被称为枚举

定义枚举

Rust 使用 enum 关键字定义枚举类型,例如,定义一个名为 Color 的类型,其值为 RedOrangeYellow

Rust 复制代码
enum Color {
    Red,
    Orange,
    Yellow
}

这声明了一个具有 3 个可能值的 Color 类型,称为变体构造器

使用枚举

创建枚举实例,使用 match 表达式,基于枚举变体进行操作

rust 复制代码
let c1 = Color::Red;
let c2 = Color::Orange;

match c1 {
    Color::Red => println!("Red"),
    Color::Orange => println!("Orange"),
    Color::Yellow => println!("Yellow")
}

带数据的枚举

带数据的枚举允许在每个枚举变体上附加一个或多个值。这些值可以是任何类型,包括基础类型、复合类型,甚至其他枚举类型

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

Message 枚举中

  • Quit 是一个没有关联数据的枚举变体
  • Move 是一个带有两个整数字段 xy 的枚举变体
  • Write 是一个带有一个字符串字段的枚举变体
  • ChangeColor 是一个带有三个整数字段的枚举变体,代表RGB颜色值

使用带数据的枚举

rust 复制代码
let quit_message = Message::Quit;
let move_message = Message::Move { x: 3, y: 4 };
let write_message = Message::Write("Hello".to_string());
let change_color_message = Message::ChangeColor(255, 0, 0);

match quit_message {
    Message::Quit => println!("Quit"),
    Message::Move { x, y } => println!("Move ({}, {})", x, y),
    Message::Write(text) => println!("Write: {}", text),
    Message::ChangeColor(r, g, b) => println!("ChangeColor RGB({}, {}, {})", r, g, b)
}

match move_message {
    Message::Quit => println!("Quit"),
    Message::Move { x, y } => println!("Move ({}, {})", x, y),
    Message::Write(text) => println!("Write: {}", text),
    Message::ChangeColor(r, g, b) => println!("ChangeColor RGB({}, {}, {})", r, g, b)
}
...

枚举也可以有方法,像结构体一样,使用 impl 关键字,基于 new 方法创建的 Message 实例

rust 复制代码
impl Message {
    ...

    fn handle_quit(&self) -> Message {
        // 处理 Quit 逻辑
    }
}

match quit_message {
    Message::Quit => Message::new().handle_quit(),
    ...
}

带数据的枚举在Rust中非常有用,因为允许在单个类型中封装多种不同的行为或状态,同时保持类型安全。经常用于表示命令、事件、消息或其他需要关联数据的等场景

内存中的枚举

在内存中,带有数据的枚举会以一个小型整数标签加上足以容纳最大变体中所有字段的内存块的格式进行存储。标签字段供 Rust 内部使用。它会区分由哪个构造器创建了值,进而决定这个值应该有哪些字段

rust 复制代码
enum RoughTime {
    InThePast(TimeUnit, u32),
    JustNow,
    InTheFuture(TimeUnit, u32),
}

从 Rust 1.50 开始,RoughTime 会占用 8 字节,如图

这里可以对枚举在内存的情况有个大概了解,看不懂可以直接过,一般不影响实战开发

用枚举表示富数据结构

枚举对于快速实现树形数据结构也很有用。假设一个 Rust 程序需要处理任意 JSON 数据。在内存中,任何 JSON 文档都可以表示为这种 Rust 类型的值:

rust 复制代码
use std::collections::HashMap;

enum Json {
    Null,
    Boolean(bool),
    Number(f64),
    String(String),
    Array(Vec<Json>),
    Object(Box<HashMap<String, Json>>),
}

JSON 标准指定了可以出现在 JSON 文档中的不同数据类型:null、布尔值、数值、字符串、各种 JSON 值的数组以及具有字符串键名和 JSON 值的对象

serde_json 是 Rust 的结构体序列化库,是 crates.io 上最常下载的 crate 之一

接口参数,复杂参数一般标配 JSON

这里在表示 ObjectHashMap 周围加 Box 只是为了让所有 Json 值更紧凑。在内存中,Json 类型的值占用 4 个机器字。而 String 值和 Vec 值占用 3 个机器字,Rust 又添加了一个标签字节。Null 值和 Boolean 值中没有足够的数据来用完所有空间,但所有 Json 值的大小必须相同。因此,额外的空间就用不上了。下图展示了 Json 值在内存中的实际布局的一些示例

HashMap 则更大。如果必须在每个 Json 值中为它留出空间,那么将会非常大,在 8 个机器字左右。但是 Box<HashMap> 是 1 个机器字:它只是指向堆中分配的数据的指针。甚至可以通过装箱更多字段来让 Json 更加紧凑

以下是一个表示JSON对象的例子

rust 复制代码
let json = Json::Object(vec![
    ("name".to_string(), Json::String("张三".to_string())),
    ("age".to_string(), Json::Number(30.0)),
    ("is_student".to_string(), Json::Bool(false)),
]);

泛型枚举

泛型枚举可以接受一个或多个类型参数。如 Rust 标准库中的两个例子

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

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Option枚举有两个变体:Some 和 None。Some 变体包含一个值,这个值的类型是T。T是一个类型参数,它可以是任何类型。使用Option枚举

rust 复制代码
let some_number: Option<i32> = Some(5);
let no_number: Option<i32> = None;

枚举相关内容就这么多了,不同类型的枚举,如何使用,基本操作都已经清楚了,接下来是 Rust 中的 模式

欢迎大家讨论交流,如果喜欢本文章或感觉文章有用,动动你那发财的小手点赞、收藏、关注再走呗 ^_^

微信公众号:草帽Lufei

相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
2401_857610034 小时前
多维视角下的知识管理:Spring Boot应用
java·spring boot·后端
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
代码小鑫4 小时前
A027-基于Spring Boot的农事管理系统
java·开发语言·数据库·spring boot·后端·毕业设计
颜淡慕潇6 小时前
【K8S问题系列 | 9】如何监控集群CPU使用率并设置告警?
后端·云原生·容器·kubernetes·问题解决
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙