首页 / Rust 入门教程 / Option 类型与空值处理

Rust 入门教程

Option 类型与空值处理

本教程共 78 篇 · 第 42 篇 · 更新于 2026-08-08 · 约 9 分钟阅读

RustRust 入门教程OptionSomeNone空值matchunwrap_or

本节目标:理解 Rust 为何消灭 null,用 Option<T> 显式表达”有值或无值”,并掌握 match 解构与常用方法安全地取值。

很多语言里有个 null,表示”这里什么都没有”。它看似方便,却是无数崩溃和 bug 的源头——你以为拿到的是值,实际是个空指针,一用就炸。图灵奖得主 Tony Hoare 在 2009 年公开道歉,称自己当年发明 null 是”十亿美元的错误”,因为它引发的空指针异常几十年来坑了无数程序员。

Rust 直接不提供 null,从根上杜绝这类问题。那”可能没有值”怎么表达?答案是 Option<T>——把”可能有值也可能没有”这件事,明明白白写进类型里。

1-1 Option 是什么

Option 是个枚举,定义只有两个成员:

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

一句话:一个变量要么有值 Some(T),要么为空 NoneOptionSomeNone 都在 prelude 里,直接用名字即可,不用写 Option::Some

它解决的核心问题是:变量到底有没有值,必须写在类型里。编译器强迫你在编译期就处理”空”的情况,而不是运行时才爆雷。这叫”用类型把 null 关进笼子”。

1-2 用 match 解构 Option

最基础的处理方式是 match,把 SomeNone 都覆盖:

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

let five = Some(5);
let six = plus_one(five); // Some(6)
let none = plus_one(None); // None

plus_one 收一个 Option<i32>,返回也是 Option<i32>。这种”进 Option、出 Option”的函数在标准库里到处都是。

匹配时,None 分支直接返回 None 不做处理;Some(i) 用模式把里面的值绑到 i,加一后用 Some 重新包好返回。注意 Some(i)i 是通过模式匹配自动解出来的,不需要你手动拆。

Tip

因为 match 要求穷尽,你没法只写 Some 漏掉 None——编译器逼你把”空”也考虑进去。这就是 Option 比 null 安全的地方:空值不再是一个能悄悄溜过的意外。

1-3 常用取值方法

除了 matchOption 还提供一组方便的方法来取值,日常很常用:

let x = Some(5);
let y: Option<i32> = None;

// 是否为有值 / 无值
assert!(x.is_some());
assert!(y.is_none());

// 有值就取出,无值给默认
let a = x.unwrap_or(0); // 5
let b = y.unwrap_or(0); // 0

// 有值就对其中的值做变换,无值保持 None
let c = x.map(|v| v * 2); // Some(10)

// 取出值,无值就 panic(仅原型/测试用)
let d = x.unwrap(); // 5

几个要点:

  • is_some() / is_none():判断有没有值;
  • unwrap_or(默认):有值取出,没值返回你给的默认值,最安全;
  • map(f):有值就把里面的 T 传给 f 处理、结果重新包成 Some,没值就原样 None
  • unwrap() / expect():和 Result 上的一样,有值取出、没值直接 panic,只适合原型或确信有值的时候。
Warning

unwrap()None 时会 panic。正式代码里如果”没值”是可能出现的正常情况,请用 unwrap_ormatch 处理,别用 unwrap 赌运气。

1-4 还有几个实用方法

let mut x = Some(5);
// 取出里面的值并置为 None(消费式)
let taken = x.take(); // taken = Some(5), x 变成 None

let mut y = Some(3);
// 有值才执行闭包
y.inspect(|v| println!("值是 {v}"));

// and_then:链式处理,只有有值时才继续
let z = Some(2).and_then(|v| Some(v * 10)); // Some(20)

take 常用于”把值从某个结构里拿走”的场景;and_then 类似 map,但闭包返回的是 Option,适合连续的多步可选计算。

1-5 Option 与 ? 的配合

上一章讲过,? 也能用在 Option 上:遇到 None 直接返回 None。这让它和 Result 一样好用:

fn last_char_of_first_line(text: &str) -> Option<char> {
    text.lines().next()?.chars().last()
}

.next() 返回 Option:是 None 就整体返回 None 不再继续;是 Some 才接着处理。这种”链式里随时提前返回”的写法非常清爽。

1-6 为什么 Option 比 null 好

用 null 的语言里,任何东西都可能偷偷是空,编译器帮不了你,只能在运行时祈祷。Rust 把”可能有值”这件事写进类型系统:一个 i32 永远有值,一个 Option<i32> 才可能为 None。你拿到 i32 时不必担心它是空,拿到 Option 时编译器逼你处理空。

类型上区分清楚,开发者心里也有底:看见 Option 就警觉”这里可能没值”,看见普通类型就放心用。这种显式的代价,换来的是大量空指针类 bug 在编译期就被消灭。

1-7 Option 的组合子:把嵌套一层层摊平

除了 matchunwrap_orOption 还有一组”组合子”方法,专门用来把多个可能为空的操作串起来,避免一层套一层的 match

最常用的是 map:有值时对其变换,无值时原样透传 Noneand_then 更特别:它的闭包本身返回 Option,所以适合”两步都可能失败”的链式查询,而不会得到 Some(Some(x)) 这种嵌套。

let n: Option<u32> = Some(3);
let doubled = n.map(|x| x * 2);          // Some(6)
let nested = n.map(|x| Some(x + 1));     // Some(Some(4)) 嵌套了
let flat   = n.and_then(|x| Some(x + 1)); // Some(4) 被摊平

let none: Option<u32> = None;
let fallback = none.or(Some(99));        // 左边是 None 时取右边:Some(99)

or / or_else 用来提供兜底值;flatten 能把 Option<Option<T>> 压成 Option<T>。这些组合子配上迭代器(后面章节会讲)非常好用,让”空值”不再打断你的逻辑流。

Tip

当心:用 map 得到 Option<Option<T>> 时,记得用 and_thenflatten 摊平,否则后续代码会一直背着两层 Some

1-8 Option 与默认值:unwrap_or 家族

Option 里的值时,除了 unwrap(没有就 panic),还有一组”温柔”的兜底方法。最常用的是 unwrap_or:有值就用值,没有就给一个备胎。

let maybe: Option<u32> = None;
let n = maybe.unwrap_or(0); // 没值,用 0

如果备胎算起来很贵,用 unwrap_or_else(|| 计算) 做惰性求值——只有真的没有值时才跑那段计算;有值时根本不会执行,省开销。unwrap_or_default() 则要求 T 实现了 Default(比如 String 的默认是空串、Vec 的默认是空数组),直接给类型的”空状态”。

另外 get_or_insert 作用在 &mut Option<T> 上:有值返回引用,没值先插入默认值再返回引用,常用来”懒初始化”一个字段。

Note

选哪个取决于”没有值时该怎么办”:固定备胎用 unwrap_or,贵备胎用 unwrap_or_else,要类型默认空状态用 unwrap_or_default,而这些本质上都比 unwrap 安全,因为不会让程序崩溃。

1-9 你的 API 该返回 Option 还是 Result

设计自己的函数时,常纠结”该返回 Option 还是 Result”。一条好记的规则是:如果”没有结果”本身就是一个合理、常见、调用方只需知道”有没有”的状态,就用 Option;如果”失败”且”为什么失败”对调用方很重要,就用 Result

翻翻标准库就能印证:HashMap::get 返回 Option,因为”键不在”是很正常的事,调用方往往只关心”有没有”;parse 返回 Result,因为”解析失败”时你通常想知道”到底哪里不合法”;iter().next() 在空迭代器上返回 Option,因为”没有下一个”是预期内状态。

所以写 API 时先问自己:这个函数”没结果”算不算正常?算,就 Option;不算、但可能出错且原因重要,就 Result。这能让调用方用最贴合的方式处理,也避免拿 Result 去装”本来就该可能为空”的语义。

Note

当”没有”和”出错”同时存在时(比如”按 id 查用户,用户可能不存在,也可能数据库连不上”),通常把”不存在”建模成 Option,把”连接出错”建模成 Result,再用 ?ok_or 把它们组合清楚。

1-10 Option 其实早已无处不在

你之前几章其实一直在用 Option,只是可能没意识到。回头看几个老朋友:

  • Vec::get(索引) 返回 Option<&T>:越界时是 None,不 panic;
  • HashMap::get(键) 返回 Option<&V>:键不存在时是 None
  • 迭代器的 .next() 在取完所有元素后返回 None,这正是 for 循环能停下来的原因;
  • 上一章的 ? 用在 Option 上,遇到 None 直接提前返回。
let v = vec![10, 20, 30];
let first = v.get(0);        // Option<&i32>:有值 -> Some(&10)
let out = v.get(99);         // Option<&i32>:越界 -> None

if let Some(x) = first {
    println!("第一个元素是 {x}");
}

把这几个散落的用法串起来你会发现:Option 不是某个孤立的语法,而是 Rust 表达”可能为空”的统一语言。从集合取值、到迭代、到错误处理,处处是它。习惯了”看见 Option 就先想 None”的思维方式,空值 bug 几乎无机可乘。

Note

一个小提醒:直接对 Option== 比较是可以的,Some(1) == Some(1)trueSome(1) == Nonefalse。但比较的是”整体是否相等”,包括”有没有值”这一层。

1-11 小结

  • Rust 没有 null,用 Option<T>Some(T) / None)显式表达”可能有值”;
  • match 解构,穷尽匹配逼你处理 None
  • 常用方法:is_some/is_noneunwrap_ormaptakeand_then
  • unwrap/expectNone 会 panic,只用于原型或确信有值;
  • ? 可用于 Option,遇 None 提前返回;
  • 类型上区分”一定有值”和”可能为无”,让空值 bug 在编译期现形。

下一章进入泛型,看看怎么写出”一套代码适配多种类型”的函数和结构体。