Rust 语言从入门到实战
唐刚
Rust 语言中文社区联合创始人
5266 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 36 讲
Rust 语言从入门到实战
15
15
1.0x
00:00/00:00
登录|注册

06|复合类型(下):枚举与模式匹配

你好,我是 Mike。今天我们一起来学习 Rust 中的枚举(enum)和模式匹配(pattern matching)。
枚举是 Rust 中非常重要的复合类型,也是最强大的复合类型之一,广泛用于属性配置、错误处理、分支流程、类型聚合等场景中。学习完这节课后,你会对 Rust 的地道风格有新的认识。

枚举:强大的复合类型

枚举是这样一种类型,它容纳选项的可能性,每一种可能的选项都是一个变体(variant)。Rust 中的枚举使用关键字 enum 定义,这点与 Java、C++ 都是一样的。与它们不同的是,Rust 中的枚举具有更强大的表达能力。
在 Rust 中,枚举中的所有条目被叫做这个枚举的变体。比如:
enum Shape {
Rectangle,
Triangle,
Circle,
}
定义了一个形状(Shape)枚举,它有三个变体:长方形 Rectangle、三角形 Triangle 和圆形 Circle。
枚举与结构体不同,结构体的实例化需要所有字段一起起作用,而枚举的实例化只需要且只能是其中一个变体起作用

负载

Rust 中枚举的强大之处在于,enum 中的变体可以挂载各种形式的类型。所有其他类型,比如字符串、元组、结构体等等,都可以作为 enum 的负载(payload)被挂载到其中一个变体上。比如,扩展一下上面的代码示例。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Rust中的枚举和模式匹配是非常强大和灵活的语法特性。枚举在Rust中具有强大的表达能力,可以容纳各种可能的选项,并且每个选项都是一个变体。枚举的变体可以挂载各种类型的负载,包括结构体、元组、字符串等,这使得枚举在属性配置、错误处理等场景中具有广泛的应用价值。模式匹配是Rust中与枚举搭配使用的重要语法,可以用于判断或匹配值是哪一个枚举的变体,并且可以有返回值。除了配合枚举进行分支管理外,模式匹配还可以与其他基础类型结合进行分支分派,非常灵活。Rust还提供了if let和while let等语法层面的设施,用于简化模式匹配的代码量。总的来说,Rust中的枚举和模式匹配能够帮助开发者更好地处理复杂的数据类型和流程控制,是Rust语言中的重要特性之一。文章还介绍了在Rust中使用ref和ref mut关键字进行模式匹配,以及在函数参数中应用模式匹配的示例。这些内容展示了Rust语言中模式匹配的灵活性和强大表达能力。文章还提到了枚举和模式匹配在实际场景中的应用,以及对读者提出了关于match表达式中不同返回类型的处理方式的思考题。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Rust 语言从入门到实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(28)

  • 最新
  • 精选
  • 小云同学
    老师,看到里面的一个例子,我产生了一些疑问。我先举个例子: ``` fn main() { let s = String::from("Hello"); let p = &s; let s2 = *p; } ``` 如果直接将 s 赋值给 s2,那么毫无疑问会发生移动,s 不再有效。但如果是对 s 的引用进行解引用,那么编译器会提示无法移动,这是啥原因呀。我自己有一个猜测,因为 Rust 默认不会深度拷贝数据,所以如果 let s2 = *p 这条语句成立,就意味着要夺走 s 的所有权。但我们之所以要获取引用,就是为了不夺走原有变量(s)的所有权,于是在这种情况下,Rust 干脆提示不允许我们移动,除非它实现了 Copy trait,数据全部在栈上,浅拷贝之后数据彼此独立。 这样理解是正确的吗?Rust 的一些概念比较相似,容易出现混乱,所以想问问老师。 基于上面这个例子,再来看看文中的一个例子。 ``` enum MyEnum { Add, Subtract, } impl MyEnum { fn run(&self, x: i32, y: i32) -> i32 { // 这里的 self 显然是枚举的某个变体的引用 // 因为 MyEnum 没有实现 Copy trait,所以它和字符串一样,不能通过解引用赋值 // let obj = *self; 这里是不合法的,由于不是可 Copy 的,因此无法移动 // 但问题来了,为啥下面的代码是合法的,原文是 match self,但改成 match *self 也可以 match *self { Self::Add => x + y, Self::Subtract => x - y, } } } ``` 因此这是我的第二个疑问,为啥 let obj = *self 不合法,但 match *self 就是合法的。 还有第三个疑问,可能是受到 C 的影响,因为变量和指针是无法比较的。所以在看到 match self 的一瞬间,就忍不住试了一下 match *self,因为参数是 &self,所以 self 是枚举变体的引用。而 Self::Add 和 Self::Subtract 是具体的枚举变体,它们之间比较总觉得有些别扭,还是 match *self 看着顺眼。所以想问一下老师,为啥这两者能够比较。 以上就是我的一些疑问,还麻烦唐老师指导一下,Rust 的一些概念有点让人头晕。

    作者回复: 非常用心的思考。反过来回答: 3. match的时候,rust做了自动解引用,就是自动加了*self。这点上Rust就是与C不同,Rust有点猜测程序员的意图的味道。比如看如下代码,也是类似做了自动解引用。 ``` fn foo(a: &u32, b: &u32) { if a > b { println!("111"); } else { println!("222"); } } fn main() { foo(&5, &4); } ``` 2. match枚举时,这里不存在对负载的匹配捕获,因此不存在“再赋值”的操作,只是比对一下,于是跟它是Copy的还是Move的就没关系了。 1. 对,因为做了“再赋值”的操作。跟 Copy的还是Move的就有关系,所以就是你那样理解的。 Rust内部做了很多逻辑自洽的推理。

    2023-11-02归属地:中国台湾
    2
    9
  • 一个人旅行
    1. 默认情况下,struct不能进行比较,需要为Shape类型实现PartialEq trait特征。 2. 方式一:使用枚举,该枚举类型的枚举值表示一个类型。 方式二:使用特征,所有的类型都需要实现该特征

    作者回复: 可以的

    2023-11-01归属地:北京
    4
  • -Hedon🍭
    作为静态类型语言,match 返回的类型必须在编译期就被确定,也就意味着 match 必须返回相同的类型。在这个前提下,如果要返回的类型,那么切入点就只能是:“返回同一个类型,但是这个类型能表示(承载)不同的类型”,那就只能是本节课将的枚举 enum 了。 ```rust enum Number { Int(i32), Float(f64), None } fn get_number(condition: i32) -> Number { match condition { 1 => Number::Int(10), 2 => Number::Float(3.14), _ => Number::None } } fn main() { let value = get_number(1); match value { Number::Int(i) => println!("int {}", i), Number::Float(f) => println!("float {}", f), Number::None => println!("not number"), } let value = get_number(2); match value { Number::Int(i) => println!("int {}", i), Number::Float(f) => println!("float {}", f), Number::None => println!("not number"), } let value = get_number(3); match value { Number::Int(i) => println!("int {}", i), Number::Float(f) => println!("float {}", f), Number::None => println!("not number"), } } ```

    作者回复: 非常棒的叙述,思路极其清晰。👍

    2023-11-07归属地:湖北
    2
  • 下雨天
    思考题: pub enum Result<T1,T2>{ V1(T1), V2(T2) } fn get_diff_value(number:i32) -> Result<i32,i32>{ let r = match number { 1 => Result::V1(1), _ => Result::V2(2) }; r } fn main(){ let r = get_diff_value(0); match r { Result::V1(x) => println!("V1={x}"), Result::V2(x) => println!("V2={x}") } } 聚合的代价是不是需要解聚合。。。。

    作者回复: 对,包,解包还是有点麻烦

    2023-11-01归属地:湖北
    2
    2
  • 水不要鱼
    老师,我有个问题,那个 ref name 那里是不是等于 &name,如果是的话,那不是相当于把一个 &String 赋值给了 String 么,还是说 rust 会自动用 &String 再去创建一个 String 然后给 name

    作者回复: 直接给的引用出来,没有再创建一份新实例。

    2023-11-06归属地:广东
    1
  • Michael
    上面的 ref 章节中,User.name 是 String 类型,为什么可以通过ref给它赋值 a.name 的引用

    作者回复: 就是要得到引用才引入的ref

    2023-11-02归属地:中国香港
    2
    1
  • plh
    这一节 就值回票价了. 文中: [Rustc 小助手如此贴心,这种保姆级服务是你在 Java、C++ 等其他语言中感受不到的。] 个人感觉, Rustc 也像 大师样的,在编写代码的时候, 在人身边不厌其烦的指出潜在的问题,直到 编译成功.

    作者回复: 对的,一个AI人类。

    2023-11-01归属地:四川
    1
  • Taozi
    1. enum变体可以挂载的类型是否就是对应了结构体的三种形态:单元结构体、元组结构体和命名结构体。 2. match表达式不同的返回类型可以使用enum进行聚合。

    作者回复: 1. 可以这样理解。但要注意,enum中的选项名称本身是 变体,variant,不是struct name; 2。对的。

    2023-11-01归属地:上海
    1
  • 付佳伟
    思考题:match各个分支返回值类型不同时,可以将各个类型实现同一个trait ,然后用Box将返回值包裹,返回值类型定义Box<dyn trait>。不知道还有没有其他办法

    作者回复: 你说这个方案后面会讲到。其它方案见其它同学说的有。

    2023-11-01归属地:北京
    1
  • RobinH
    #[derive(Debug)] enum Type<T> { A(T), B(T), } fn match_type<T>(t1: Type<T>) -> T { let temp = match t1 { Type::A(a) => { a } Type::B(b) => { b } }; return temp; } 思考题 我尝试理解下了下 ,是想考 泛型么?

    作者回复: 枚举自己就能强行聚合各种类型的。

    2024-01-24归属地:美国
收起评论
显示
设置
留言
28
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部