Rust 入门:编译器是你最严格的朋友 书架

第 08 章 / 共 12 章

第八章 · 枚举与 match:把「可能没有」写进类型

「几种可能之一」怎么表达?

上一章的结构体表达「一个东西由哪些部分组成」。还有一种同样常见的形状:「一个东西是几种可能之一」。比如一次网络请求的结果:要么成功拿到网页,要么超时,要么被服务器拒绝。注意这三种可能各自带的数据还不一样——成功带一串 HTML,被拒带一个状态码,超时什么都不带。这种形状在 Rust 里的表达是枚举(enum):

enum FetchResult {
    Success(String),   // 带一串 HTML
    Rejected(u16),     // 带一个状态码
    Timeout,           // 什么都不带
}

别被其他语言里「枚举就是给数字起名字」的印象带偏。Rust 的枚举是带数据的标签:每个变体(variant)可以各自挂载不同类型、不同数量的数据。一个 FetchResult 值在任一时刻必然是三者之一,不多不少——编译器担保。

match:把每一种可能都摆到台面上

拿到一个枚举值后怎么拆包?用 match

fn report(r: FetchResult) {
    match r {
        FetchResult::Success(html) => println!("拿到 {} 字节", html.len()),
        FetchResult::Rejected(code) => println!("被拒了,状态码 {}", code),
        FetchResult::Timeout => println!("超时了"),
    }
}

match 逐条比对变体,命中哪条执行哪条,变体里挂的数据就地拆出来用。真正厉害的是这条铁律:match 必须穷尽所有可能。漏写一个变体,编译不过。将来你给枚举加了第四种可能,所有拆包它的 match 全部报错,逼着你挨个处理——重构从此有了保镖。

类比:别的语言处理「几种可能之一」像查电表,漏看一格就漏收一户的钱,而且没人提醒你;Rust 的 match 像出卷老师发答题卡——格子都印好了,漏填任何一格,当场交不了卷。

有时候你只关心一种可能,写全 match 太隆重,可以用 if let

if let FetchResult::Success(html) = r {
    println!("成功!{}", html.len());
}   // 其他可能不管了

读作「如果 r 恰好是 Success 这种,就把 html 拆出来执行这段」。便利的代价是放弃了穷尽检查——只在你真的不在乎其它分支时用。

Option:把「可能没有」写进类型系统

现在讲枚举最重要的应用,也是 Rust 对整个编程世界的回答。先看一段每个程序员都写过的灾难:某语言里取用户头像,返回 null 表示「没有头像」,然后某处忘了判空——程序炸了。null 的发明者 Tony Hoare 自己管它叫「十亿美元的错误」:问题不在于 null 这个概念,而在于「可能是 null」这件事在类型上看不出来,编译器无从检查你判没判。

Rust 的标准库里没有 null。取而代之的是枚举 Option

enum Option<T> {
    Some(T),   // 有,里面装着值
    None,      // 没有
}

<T> 是泛型参数,表示「里面装什么类型都行」,第十一章细讲。)一个函数可能找不到结果,它的返回类型就写 Option<User>,而不是「返回 User 但可能偷偷是 null」。区别在哪?你想拿到里面的 User,必须经过一道拆包——None 的情况不处理,你就碰不到值:

let maybe = find_user(42);   // 类型是 Option<User>,有没有一看签名就知道
match maybe {
    Some(u) => println!("找到了:{}", u.name),
    None => println!("查无此人"),
}

null 像一颗混进糖罐的图钉——看起来是糖,咬下去才知道;Option 像一个贴着「可能为空」警示标签的盒子——你必须开盒验货才能吃到糖。「十亿美元的错误」在 Rust 里的修复方案就这么朴素:把可能性从「暗坑」变成「明牌」。

Option 还配了一组顺手的工具方法:.unwrap_or(默认值) 给个兜底,.map(f) 有值就加工一下,.is_some() 只问有没有。日常代码里它们比手写 match 更常用,但骨子里都是同一个枚举。

「可能没有」有了明牌表达,下一个问题顺理成章:「可能失败」呢?读取文件可能失败、网络请求可能失败——这类「失败」和「没有」不一样,失败通常带着原因。下一章的 Result 和那个问号运算符,是 Rust 日常代码里出场率最高的搭档。

Rust 入门:编译器是你最严格的朋友 · 王建硕
费曼式写法 · 由多个 AI 代理撰写与互相审校