ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Rust 枚举模式匹配:深入掌握 match 控制流与穷尽性检查(The Rust Programming Language 官方实践)

Rust 枚举模式匹配:深入掌握 match 控制流与穷尽性检查(The Rust Programming Language 官方实践) 教程文档【免费下载链接】bookThe Rust Programming Language项目地址https://gitcode.com/gh_mirrors/bo/book点击查看免费下载match是 Rust 中最强大的控制流结构之一它将一个值与一系列模式逐一比对并执行首个匹配模式对应的代码分支。本篇文章基于本仓库The Rust Programming Language 官方书稿src/ch06-02-match.md 的完整教学内容展开结合仓库内可编译、可运行的代码清单系统讲解match的语法、模式绑定、OptionT处理、穷尽性检查以及 catch-all 与_占位符的使用。读完本篇你将能够用match写出编译器帮你保证所有情况都已处理的健壮枚举处理逻辑。什么是match值、模式与代码分支match允许你把一个值同一系列模式pattern比较然后根据哪个模式匹配来执行相应代码。模式可以由字面量、变量名、通配符等多种元素构成——关于全部模式种类的详解参见 src/ch19-00-patterns.md本书第 19 章。match的强大之处来自模式的表达能力以及编译器会确认所有可能的情况都已被处理这一事实。可以把match表达式想象成一台硬币分拣机硬币沿轨道滑下轨道上排列着大小不一的孔洞每个硬币会落入它能通过的第一个孔洞。同理值会依次经过match中的每个模式在第一个恰好适合的模式处落入对应的代码块并被执行。第一个例子为美国硬币计算面值Listing 6-3下面是仓库中listings/ch06-enums-and-pattern-matching/listing-06-03/src/main.rs的完整代码。它定义了一个Coin枚举并编写函数value_in_cents接收一个未知的 US 硬币类似计数机那样判断硬币种类并返回其面值单位分enum Coin { Penny, Nickel, Dime, Quarter, } fn value_in_cents(coin: Coin) - u8 { match coin { Coin::Penny 1, Coin::Nickel 5, Coin::Dime 10, Coin::Quarter 25, } }逐行拆解这个match先写出match关键字后面跟一个表达式本例中是被匹配的值coin。这看起来与if后面的条件表达式很像但有一个重大区别if的条件必须求值为布尔值而match后面可以是任意类型。本例中coin的类型是文件开头定义的Coin枚举。接下来是match的各个分支arm。一个分支由两部分组成一个模式 一段代码。第一个分支的模式是Coin::Penny运算符把模式与要运行的代码分隔开这里的代码只是值1。每个分支之间用逗号分隔。当match表达式执行时它会按顺序把结果值与每个分支的模式比较。若某个模式匹配就执行该模式关联的代码若不匹配则继续检查下一个分支——正如硬币分拣机那样。分支数量没有上限本例中有四个分支。每个分支的代码都是一个表达式匹配分支中表达式的结果值就是整个match表达式返回的值。多行分支使用花括号当分支代码很短例如只返回一个值时通常不用花括号。但如果一个分支要运行多行代码就必须使用花括号此时该分支后的逗号可以省略。仓库中的listings/ch06-enums-and-pattern-matching/no-listing-08-match-arm-multiple-lines/src/main.rs演示了这一点——每次传入Coin::Penny时打印 Lucky penny!但仍返回代码块的最后一个值1fn value_in_cents(coin: Coin) - u8 { match coin { Coin::Penny { println!(Lucky penny!); 1 } Coin::Nickel 5, Coin::Dime 10, Coin::Quarter 25, } }注意println!(Lucky penny!);语句之后没有分号结尾的1是块表达式的尾表达式它决定了整个分支代码块的值。模式绑定从枚举变体中提取值match分支的另一个实用特性是模式可以绑定到与之匹配的值的各个部分。这正是从枚举变体中提取数据的核心手段。沿用硬币的例子1999 至 2008 年间美国为 50 个州各发行了一版不同图案的 25 分硬币quarter。没有其他硬币带有州图案所以只有Quarter变体带有额外数据。我们可以把Quarter变体改成包含一个UsState值如listings/ch06-enums-and-pattern-matching/listing-06-04/src/main.rs所示#[derive(Debug)] // so we can inspect the state in a minute enum UsState { Alabama, Alaska, // --snip-- } enum Coin { Penny, Nickel, Dime, Quarter(UsState), }想象一下一位朋友正在收集全部 50 个州的 25 分硬币。我们在按硬币种类分拣零钱的同时还要喊出每个 25 分币所属的州名以便朋友能补上缺失的那枚。在对应的match表达式中我们为匹配Coin::Quarter变体的模式添加一个名为state的变量。当Coin::Quarter匹配时state变量会绑定到该 25 分币所属州的值上随后即可在分支代码中使用它如listings/ch06-enums-and-pattern-matching/no-listing-09-variable-in-pattern/src/main.rsfn 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 } } }若调用value_in_cents(Coin::Quarter(UsState::Alaska))此时coin就是Coin::Quarter(UsState::Alaska)。依次与各分支比较时前面的分支都不匹配直到到达Coin::Quarter(state)——此时state绑定为UsState::Alaska。随后println!表达式中使用{state:?}格式化这依赖UsState上#[derive(Debug)]提供的调试输出能力从而把内部的状态值从Quarter变体中取了出来。用match处理OptionT经典的 plus_one 模式上一节本书第 6 章枚举定义部分介绍了如何从Some情况中取出内部的T值现在我们用match直接处理OptionT方式与处理Coin枚举完全相同——不再比较硬币而是比较OptionT的变体match表达式的工作机制不变。假设我们要写一个函数接收一个Optioni32如果内部有值则加 1如果没有值则原样返回None、不执行任何运算。借助match这个函数很容易写出见listings/ch06-enums-and-pattern-matching/listing-06-05/src/main.rsfn plus_one(x: Optioni32) - Optioni32 { match x { None None, Some(i) Some(i 1), } } let five Some(5); let six plus_one(five); let none plus_one(None);逐分支推演执行过程先看plus_one(five)的第一次执行plus_one体内的变量x值为Some(5)。把它与第一个分支比较None None,Some(5)不匹配None模式继续下一个分支Some(i) Some(i 1),Some(5)匹配Some(i)吗匹配变体相同i绑定到Some内部包含的值于是i取得5。随后执行分支代码给i加 1并把总和6装进一个新的Some值返回。再看第二次调用plus_one(None)此时x是None。进入match后先与第一个分支比较None None,这次匹配成功由于没有值可加程序直接返回右侧的None。因为第一个分支已经匹配其余分支不再比较。match 枚举 绑定内部数据 据此执行代码是 Rust 代码中反复出现的惯用法先用match匹配枚举把变量绑定到内部数据再基于它执行业务逻辑。初学阶段会觉得有点绕但一旦习惯你会在所有语言里都想要这个特性——它一直是 Rust 使用者最偏爱的能力之一。穷尽性检查编译器强制覆盖所有可能match还有一个必须讨论的特性分支的模式必须覆盖所有可能性。仓库中listings/ch06-enums-and-pattern-matching/no-listing-10-non-exhaustive-match/src/main.rs给出了一个带 bug、无法编译的plus_one版本——它漏掉了None分支fn plus_one(x: Optioni32) - Optioni32 { match x { Some(i) Some(i 1), } }幸运的是这是 Rust 能帮你捕获的 bug。尝试编译这段代码会得到listings/ch06-enums-and-pattern-matching/no-listing-10-non-exhaustive-match/output.txt中记录的错误输出$ cargo run Compiling enums v0.1.0 (file:///projects/enums) error[E0004]: non-exhaustive patterns: None not covered -- src/main.rs:3:15 | 3 | match x { | ^ pattern None not covered | note: Optioni32 defined here ... help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown | 4 ~ Some(i) Some(i 1), 5 ~ None todo!(),可以看到Rust 不仅知道你没有覆盖所有可能的情况甚至明确指出了你遗漏的是哪一个模式Rust 中的match是**穷尽exhaustive**的要让代码合法你必须穷尽每一种可能性。尤其在OptionT的场合Rust 阻止你忘记显式处理None情况就等于保护你不再在可能没有值时却假设自己一定有值——这正是前面章节提到的十亿美元错误billion-dollar mistake即空指针假设在 Rust 中变得不可能的原因。Catch-all 模式与_占位符使用枚举时我们常常希望对少数特定值采取特殊动作而对所有其他值执行一个默认动作。设想实现一个游戏规则掷骰子掷出 3 时角色不移动而是获得一顶新帽子掷出 7 时失去一顶帽子其他点数则移动相应格数。仓库中listings/ch06-enums-and-pattern-matching/no-listing-15-binding-catchall/src/main.rs实现了这段逻辑骰子结果硬编码为 9其余逻辑用无函数体的占位函数表示因为完整实现超出本例范围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) {}前两个分支的模式是字面量3和7最后一个覆盖所有其他值的分支其模式是我们命名为other的变量。other分支的代码把这个变量传给move_player函数使用。这段代码能编译尽管我们并没有列出u8的全部可能取值因为最后一个模式会匹配所有未被明确列出的值。这个 catch-all 模式满足了match必须穷尽的要求。注意catch-all 分支必须放在最后因为模式按顺序求值——如果把它放在前面后面的分支永远不会执行而且如果你在 catch-all 之后又添加分支Rust 会发出警告用_忽略值当你想用 catch-all 但又不使用被匹配的值时可以用__是一个特殊模式它匹配任意值但不绑定该值。这告诉 Rust我们不打算使用这个值因此 Rust 不会给出未使用变量的警告。修改游戏规则现在只要掷出 3 或 7 以外的点数就重掷。我们不再需要用到 catch-all 捕获的值于是把名为other的变量换成_见listings/ch06-enums-and-pattern-matching/no-listing-16-underscore-catchall/src/main.rslet 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() {}这个例子同样满足穷尽性要求因为我们在最后一个分支里显式忽略了所有其他值没有任何遗漏。用单元值表达什么都不做再次修改规则掷出 3 或 7 以外的点数时回合内不发生任何其他事情。可以用单元值我们在第 3 章 src/ch03-02-data-types.md 的元组类型一节提到的空元组作为_分支的代码见listings/ch06-enums-and-pattern-matching/no-listing-17-underscore-unit/src/main.rslet dice_roll 9; match dice_roll { 3 add_fancy_hat(), 7 remove_fancy_hat(), _ (), } fn add_fancy_hat() {} fn remove_fancy_hat() {}这里我们在明确告诉 Rust任何不匹配前面分支模式的值我们都不会使用也不希望运行任何代码。小结与实践路径match的完整语法总结为match 表达式 { 模式 表达式, ... }其核心要点包括匹配值可以是任意类型不同于if只接受布尔条件分支由模式 代码组成代码块最后表达式的值即整个match的结果模式可以绑定内部数据如Coin::Quarter(state)、Some(i)用于从枚举变体中提取值match是穷尽的漏掉任一可能性都会触发error[E0004]编译错误catch-all 分支必须最后出现不需要值时应使用不绑定的_占位符配合单元值()表达不执行任何动作。关于模式与匹配的更多内容范围模式、|多模式、if守卫、解构等本书 src/ch19-00-patterns.md 有系统讲解。后续章节将介绍if let语法——当match表达式略显冗长时if let是更方便简洁的替代惯用法。你可以直接在仓库中运行这些可编译清单来动手验证进入对应listings/ch06-enums-and-pattern-matching/下的示例目录执行cargo run并尝试故意删掉一个分支观察编译器给出的穷尽性错误这是理解match最直观的方式。赞分享教程文档【免费下载链接】bookThe Rust Programming Language项目地址https://gitcode.com/gh_mirrors/bo/book点击查看免费下载相关推荐The Rust Programming Language枚举与模式匹配的完美结合The Rust Programming Language枚举与模式匹配的完美结合 Rust编程语言中的枚举Enum和模式匹配Pattern Match教程文档Rust 枚举与模式匹配实战Python 开发者从 Union 类型到穷尽式 match 的迁移指南Rust 枚举与模式匹配实战Python 开发者从 Union 类型到穷尽式 match 的迁移指南 本指南是 RustTraining 仓库《Rust fo文档教程Rust 模式匹配完全指南深入解析《The Rust Programming Language》第 19 章 Patterns and MatchingRust 模式匹配完全指南深入解析《The Rust Programming Language》第 19 章 Patterns and Matching 模式教程文档上一篇Windows文件资源管理器STL缩略图预览让3D模型管理变得像图片一样简单下一篇Obsidian Image Toolkit如何在Obsidian中实现专业级图片管理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表