07|枚举与 Option:明确处理不同情况
目标:用枚举和穷尽匹配处理有限状态,避免把“没有结果”伪装成正常值。
命令行工具经常面对几个互斥选择:显示字节数或显示字符数、拿到了参数或没有参数。枚举把这些选择明确写进类型中。结构体要求多个字段同时存在,枚举则从若干变体中选择一种;变体还可以携带数据。
标准库的 Option<T> 就是这样的枚举:Some(T) 带着一个值,None 表示没有值。这里的 <T> 表示里面的值可以是不同类型;本课只需要会使用,第 10 课再自己定义泛型接口。match 匹配变体,并要求覆盖所有可能情况。少处理一种状态会在编译时暴露,而不是等到某位用户遇到空输入。
把缺省选择写明白
创建 examples/options.rs:
enum Metric {
Bytes,
Chars,
}
fn measure(text: &str, metric: Metric) -> usize {
match metric {
Metric::Bytes => text.len(),
Metric::Chars => text.chars().count(),
}
}
fn main() {
let text = "你好 Rust";
let first = text.split_whitespace().next();
match first {
Some(word) => println!("first={word}"),
None => println!("first=<empty>"),
}
println!("bytes={}", measure(text, Metric::Bytes));
println!("chars={}", measure(text, Metric::Chars));
}
next() 从迭代器取下一项,可能没有,所以结果是 Option<&str>。Some(word) 一边识别变体,一边把里面的文本引用绑定为 word。=> 右边是对应分支的表达式。
运行前先预测:没有词时,代码会返回空字符串还是进入另一个分支?
cargo run --quiet --example options
预期输出:
first=你好
bytes=11
chars=7
两个统计分支都返回 usize,因此整个 match 表达式可以作为函数返回值。枚举让调用者不能传入一个拼错的模式字符串;字符串到枚举的解析应在输入边界处理。
验证空输入不是意外
- 把
text改为" "。 - 再运行,第一行应为
first=<empty>,后两行为bytes=3、chars=3。 - 暂时删掉
None分支,运行cargo check --example options,应报告模式未覆盖完整。 - 恢复分支和原输入。空白字符串有长度,却没有按空白分隔的词,这两件事并不矛盾。
独立练习:新增 Metric::Words,并在 measure 中处理。使用断言验证 measure("你好 Rust", Metric::Words) 为 2、measure("", Metric::Words) 为 0。
参考修改
完整替换原枚举及函数,原 main 保留并在末尾加入题目中的两条断言:
enum Metric {
Bytes,
Chars,
Words,
}
fn measure(text: &str, metric: Metric) -> usize {
match metric {
Metric::Bytes => text.len(),
Metric::Chars => text.chars().count(),
Metric::Words => text.split_whitespace().count(),
}
}
这是一段依赖原 main 的替换代码。两条完整断言为 assert_eq!(measure("你好 Rust", Metric::Words), 2); 和 assert_eq!(measure("", Metric::Words), 0);。同一运行命令成功且仍输出原来的三行,即通过验收。
如果你想用 unwrap() 直接取出 Some,先确认 None 是否确实不可能发生。用户输入为空是正常情况,应明确处理。如果添加枚举变体后编译失败,顺着诊断补齐各处 match,这正是枚举帮助你发现遗漏的方式。对需要随新增变体一起更新的逻辑,避免过早用 _ 吞掉其余情况。
本课新增 examples/options.rs。最终 CLI 也会用 Option 检查参数是否存在。