4.2 枚举与模式匹配:案由分类


4.2 枚举与模式匹配:案由分类

本节摘要:枚举把"互斥的几种情况"连同各自携带的数据建成一个类型,match 按分支解构处理并强制穷尽。本节以标准库 Option 与 Result 两大内置枚举为判例,展示"让非法状态不可表示"的建模思路。读完你能用枚举加 match 替代布尔标志与魔法值。

枚举:每个变体可携带不同数据

enum Ruling { Acquitted, // 无数据 Fine(u32), // 携带一个值 Sentence { years: u8, parole: bool }, // 携带命名字段 } fn describe(r: &Ruling) -> String { match r { Ruling::Acquitted => "无罪释放".into(), Ruling::Fine(amount) => format!("罚款 {}", amount), Ruling::Sentence { years, .. } => format!("监禁 {} 年", years), } }

与 C 枚举(本质是整数常量)不同,Rust 枚举变体是真正的和类型:值恰为其一,且可携带载荷。match 在解构时把载荷取出来用,.. 忽略其余字段。

判例:Option 消灭空指针

fn find_case(no: u32) -> Option<String> { if no == 173 { Some("所有权纠纷".into()) } else { None } } let name = match find_case(173) { Some(n) => n, None => "未立案".to_string(), }; let short = find_case(42).unwrap_or_default();

Option 表示"可能没有"。没有就是 None,不存在 null 那种"任何引用类型都可能为空"的全域风险。编译器强迫你在取出内容前先过 match、unwrap 或 ?——空指针解引用这类案件在源头上没有立案资格。

match 的附加权力

let msg = match n { 0 => "零", 1 | 2 | 3 => "少", // 多模式或 4..=9 => "中", // 范围模式 _ => "多", };

绑定、守卫(if 条件附加在臂上)、嵌套解构都可组合。if let 是单分支速记:只关心一种情况时替代完整 match。

图:非法状态建模对比

图:非法状态建模对比

要点回顾

  • 变体可携带异构载荷,枚举是和类型不是整数常量;
  • Option 取代可空引用,处理缺失成为类型义务;
  • match 穷尽性 + 解构是 Rust 表达力的中枢,第 6 章 Result 沿用同一套;
  • if let 适合单分支,别为一种情况写全量 match。

💡 关键直觉:建模时先问"哪些状态组合不该存在",把它们设计成无法构造的枚举分支之外的路径。

守卫与绑定:match 的两枚精密量具

match 的分支不只能比对形态,还能加条件(守卫)与捕获子结构(@ 绑定),这两者把分派逻辑写成一段声明。

#[derive(Debug)] enum Motion { Adjourn(u32), // 申请休庭,附时长 Amend { field: String }, } fn rule_on(m: &Motion) -> &'static str { match m { Motion::Adjourn(n) if *n > 120 => "时长过长,驳回", Motion::Adjourn(_) => "准许休庭", Motion::Amend { field } if field == "被告" => "改被告须重新送达", Motion::Amend { .. } => "程序性修改,准许", } } fn classify(page: u16) -> String { match page { 0 => String::from("空页"), low @ 1..=9 => format!("目录区 {}", low), // @ 把范围值绑定下来 10..=99 => String::from("正文"), _ => String::from("附录"), } } fn main() { println!("{}", rule_on(&Motion::Adjourn(180))); println!("{}", classify(3)); }

守卫里的 | 多模式与 if 组合时注意:守卫作用于整个分支的全部模式,不能只约束其中一个。范围搭配 @ 是避免"匹配了范围却拿不到值"的标准写法。

Option 的两道高频判例

Option 出庭最多的现场是"查表",两个惯用法值得固化为肌肉记忆。

fn main() { let mut shelf: Vec<Option<String>> = vec![Some(String::from("A")), None, Some(String::from("C"))]; // 判例一:迭代时保留 None 的坑——iter 会产出 &Option for slot in shelf.iter_mut() { if let Some(item) = slot { item.push('!'); } } println!("{:?}", shelf); // 判例二:链式取值,避免层层 if let let record: Option<&Option<String>> = shelf.first(); let label = record .and_then(|s| s.as_ref()) // Option<&Option<T>> → Option<&T> .map(|s| s.len()) .unwrap_or(0); println!("首格长度 {}", label); }

Vec<Option<T>> 这个组合本身值得多说一句:它把"位置存在但内容缺席"与"位置不存在"分开表达,这是 null 语义的拆分,也是与空数组约定最大的差异。组合子 and_then / map / unwrap_or 各管一段,链起来之后控制流完全消失,只剩数据变换。

穷尽性是一份免费的审查

match 漏写形态时 E0004 出庭,报错会列出全部未覆盖形态。工程上的推论:宁可枚举多一个形态、match 多一个分支,也不要用 _ 把未知形态吞掉——_ 是穷尽性检查的逃生门,随手写它等于把免费的陪审员请出法庭。协议解析、状态机这类代码里,_ 分支应至少带 debug_assert 或日志。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U