Rust Option 详解:安全处理“可能存在,也可能不存在”的值

作者:程序员爱钓鱼日期:2026/7/26

《Rust 编程实战》系列第 21 篇

在上一篇文章中,我们学习了 HashMap。

在使用 HashMap 查询数据时,经常会看到这样的代码:

1map.get("Tom")
2

它的返回值并不是直接返回某个值,而是:

1Option<&V>
2

为什么?

因为指定的键可能存在,也可能不存在。

Rust 使用:

1Option<T>
2

来表示这种“可能有值,也可能没有值”的情况。

它是 Rust 中最重要的枚举类型之一。

本文将介绍:

  • 什么是 Option<T>
  • Some 与 None
  • 为什么 Rust 不依赖传统空值
  • match 处理 Option
  • if let
  • unwrap() 与 expect()
  • 默认值处理
  • map()、and_then()
  • 函数返回 Option
  • 实战案例
  • 常见错误与最佳实践

什么是 Option?

Option<T> 的定义可以简单理解为:

1enum Option<T> {
2    Some(T),
3    None,
4}
5

它只有两种状态。

有值:

1Some(value)
2

没有值:

1None
2

例如:

1fn main() {
2    let first = Some(100);
3    let second: Option<i32> = None;
4
5    println!("{:?}", first);
6    println!("{:?}", second);
7}
8

输出:

1Some(100)
2None
3

为什么需要 Option?

假设我们要查询一个用户年龄。

用户可能存在:

120
2

也可能不存在。

一些语言可能使用:

1null
2

表示没有值。

但如果忘记检查空值,就可能产生运行时错误。

Rust 更倾向于把“可能不存在”明确写进类型:

1Option<i32>
2

这意味着:

调用者必须意识到这个值有可能不存在。


Some 与 None

有值时使用:

1Some()
2

例如:

1let age = Some(20);
2

它的类型通常是:

1Option<i32>
2

没有值时:

1let age: Option<i32> = None;
2

这里通常需要明确类型,因为单独一个 None 无法告诉编译器内部应该是什么类型。


Option 与 T 不同

下面两个变量类型完全不同:

1let age: i32 = 20;
2
3let optional_age: Option<i32> =
4    Some(20);
5

不能直接这样相加:

1let result = age + optional_age;
2

因为:

1i32
2

和:

1Option<i32>
2

不是同一种类型。

必须先把 Option 中的值安全地取出来。


使用 match 处理 Option

最完整的处理方式是:

1match
2

例如:

1fn main() {
2    let score = Some(90);
3
4    match score {
5        Some(value) => {
6            println!("成绩:{}", value);
7        }
8        None => {
9            println!("没有成绩");
10        }
11    }
12}
13

输出:

1成绩:90
2

这里必须处理:

1Some
2

和:

1None
2

两个分支。


函数返回 Option

假设我们实现一个查找函数:

1fn find_user(id: u64)
2    -> Option<&'static str>
3{
4    match id {
5        1 => Some("Tom"),
6        2 => Some("Alice"),
7        _ => None,
8    }
9}
10
11fn main() {
12    match find_user(2) {
13        Some(name) => {
14            println!("用户:{}", name);
15        }
16        None => {
17            println!("用户不存在");
18        }
19    }
20}
21

当用户存在时:

1Some("Alice")
2

不存在时:

1None
2

这种设计比返回一个特殊字符串:

1"不存在"
2

更加明确。


使用 if let 简化 Option

如果只关心 Some,可以使用:

1if let
2
1fn main() {
2    let username = Some("Tom");
3
4    if let Some(name) = username {
5        println!("用户名:{}", name);
6    }
7}
8

相当于只处理:

1Some
2

而忽略 None。

如果还需要处理 None:

1if let Some(name) = username {
2    println!("用户名:{}", name);
3} else {
4    println!("用户名不存在");
5}
6

使用 unwrap() 获取值

可以使用:

1unwrap()
2

直接获取内部值:

1fn main() {
2    let number = Some(100);
3
4    let value = number.unwrap();
5
6    println!("{}", value);
7}
8

输出:

1100
2

但是如果:

1let number: Option<i32> = None;
2

再执行:

1number.unwrap();
2

程序会 panic。

因此:

不确定一定有值时,不要随意使用 unwrap()。


使用 expect()

expect() 与 unwrap() 类似,但可以指定错误信息。

1fn main() {
2    let config = Some("127.0.0.1");
3
4    let host = config.expect(
5        "服务器地址不能为空"
6    );
7
8    println!("{}", host);
9}
10

如果为 None,错误信息会更加明确。

在测试代码或程序启动阶段,如果某个值理论上必须存在,expect() 通常比 unwrap() 更容易排查问题。


使用 unwrap_or() 设置默认值

如果值不存在,可以提供默认值:

1fn main() {
2    let score: Option<i32> = None;
3
4    let value = score.unwrap_or(0);
5
6    println!("{}", value);
7}
8

输出:

10
2

如果:

1let score = Some(90);
2

则得到:

190
2

unwrap_or_else()

如果默认值需要通过计算获得,可以使用:

1unwrap_or_else()
2
1fn default_name() -> String {
2    String::from("游客")
3}
4
5fn main() {
6    let username: Option<String> = None;
7
8    let name = username
9        .unwrap_or_else(default_name);
10
11    println!("{}", name);
12}
13

只有在 None 时,才会调用默认值函数。


使用 map() 转换 Option

假设:

1let value = Some(10);
2

想把内部数字乘以 2。

可以使用:

1map()
2
1fn main() {
2    let value = Some(10);
3
4    let result =
5        value.map(|number| number * 2);
6
7    println!("{:?}", result);
8}
9

输出:

1Some(20)
2

如果原值是:

1None
2

则结果仍然是:

1None
2

所以 map() 可以理解为:

有值就处理,没有值就继续保持 None。


Option 与字符串处理

例如:

1fn main() {
2    let username =
3        Some(String::from("tom"));
4
5    let result = username.map(|name| {
6        name.to_uppercase()
7    });
8
9    println!("{:?}", result);
10}
11

输出:

1Some("TOM")
2

使用 and_then()

有时候一次操作之后,仍然可能没有值。

例如:

1fn parse_age(text: &str)
2    -> Option<u32>
3{
4    text.parse::<u32>().ok()
5}
6

进一步处理:

1fn valid_age(age: u32)
2    -> Option<u32>
3{
4    if age <= 150 {
5        Some(age)
6    } else {
7        None
8    }
9}
10

可以组合:

1fn main() {
2    let result = parse_age("20")
3        .and_then(valid_age);
4
5    println!("{:?}", result);
6}
7

输出:

1Some(20)
2

如果输入:

1abc
2

或者年龄超过限制,都会得到:

1None
2

使用 Option 查询 Vec

Vec::get() 返回:

1Option<&T>
2

示例:

1fn main() {
2    let numbers = vec![10, 20, 30];
3
4    match numbers.get(5) {
5        Some(value) => {
6            println!("值:{}", value);
7        }
8        None => {
9            println!("索引不存在");
10        }
11    }
12}
13

相比:

1numbers[5]
2

get() 不会因为越界直接 panic。


使用 Option 查询 HashMap

1use std::collections::HashMap;
2
3fn main() {
4    let mut users = HashMap::new();
5
6    users.insert("Tom", 100);
7
8    if let Some(score) = users.get("Tom") {
9        println!("积分:{}", score);
10    }
11}
12

get() 返回:

1Option<&i32>
2

因为对应的键可能不存在。


实战:查找最高成绩

1fn find_max(scores: &[i32])
2    -> Option<i32>
3{
4    if scores.is_empty() {
5        return None;
6    }
7
8    let mut max = scores[0];
9
10    for &score in scores {
11        if score > max {
12            max = score;
13        }
14    }
15
16    Some(max)
17}
18
19fn main() {
20    let scores = [80, 95, 72, 100];
21
22    match find_max(&scores) {
23        Some(max) => {
24            println!("最高分:{}", max);
25        }
26        None => {
27            println!("没有成绩");
28        }
29    }
30}
31

如果切片为空:

1[]
2

返回:

1None
2

这样比返回:

10
2

更合理,因为 0 本身可能就是合法成绩。


实战:查询商品价格

1use std::collections::HashMap;
2
3fn find_price(
4    products: &HashMap<String, f64>,
5    name: &str,
6) -> Option<f64> {
7    products.get(name).copied()
8}
9
10fn main() {
11    let mut products = HashMap::new();
12
13    products.insert(
14        String::from("键盘"),
15        299.0,
16    );
17
18    products.insert(
19        String::from("鼠标"),
20        99.0,
21    );
22
23    match find_price(&products, "键盘") {
24        Some(price) => {
25            println!("价格:{:.2}", price);
26        }
27        None => {
28            println!("商品不存在");
29        }
30    }
31}
32

这里:

1products.get(name)
2

得到:

1Option<&f64>
2

调用:

1copied()
2

可以转换为:

1Option<f64>
2

因为 f64 实现了 Copy。


常见错误

把 Option 当成普通值使用

错误:

1let value = Some(10);
2
3println!("{}", value + 1);
4

必须先处理 Option:

1if let Some(number) = value {
2    println!("{}", number + 1);
3}
4

滥用 unwrap()

不推荐:

1let user = map.get("Tom").unwrap();
2

如果 Tom 不存在,程序会 panic。

更推荐:

1match map.get("Tom") {
2    Some(user) => {
3        println!("{:?}", user);
4    }
5    None => {
6        println!("用户不存在");
7    }
8}
9

用特殊值表示“没有数据”

例如:

1fn find_score() -> i32 {
2    -1
3}
4

问题是:

1-1
2

到底表示真实数据,还是不存在?

更加明确的设计是:

1fn find_score() -> Option<i32>
2

最佳实践

如果值可能不存在,优先考虑:

1Option<T>
2

例如:

1fn find_user(id: u64)
2    -> Option<User>
3

不要随意使用:

1unwrap()
2

可以优先考虑:

1match
2if let
3unwrap_or()
4map()
5and_then()
6

函数只需要表达“有没有值”时,Option 非常合适。

但如果不仅要告诉调用者失败,还需要说明:

为什么失败?

那么就应该使用:

1Result<T, E>
2

Option 和 Result 的区别

Option:

1Some(value)
2None
3

表达:

1有值 / 没值
2

例如:

1HashMap::get()
2Vec::get()
3

而 Result:

1Ok(value)
2Err(error)
3

表达:

1成功 / 失败,并携带错误原因
2

例如读取文件:

1成功 -> 文件内容
2失败 -> 文件不存在、权限不足等错误信息
3

可以简单理解:

不需要解释为什么没有值,使用 Option。

需要说明为什么失败,使用 Result。


本章小结

Option<T> 用来安全表达一个值可能存在,也可能不存在。

它包含:

1Some(T)
2

和:

1None
2

通过本文,我们学习了:

  • 什么是 Option<T>
  • Some 与 None
  • 为什么值不能直接与 Option<T> 混用
  • 使用 match 处理 Option
  • 使用 if let 简化代码
  • unwrap() 与 expect()
  • unwrap_or() 和 unwrap_or_else()
  • 使用 map() 转换值
  • 使用 and_then() 组合操作
  • Vec::get() 返回 Option
  • HashMap::get() 返回 Option
  • 使用 Option 设计安全的函数返回值
  • Option 和 Result 的区别

可以记住:

Option<T> 不是一个需要绕开的麻烦,而是 Rust 强迫我们显式处理“没有数据”这种真实情况。


下一篇预告

下一篇我们将学习 Rust Result 详解:错误处理的核心机制,包括:

  • 什么是 Result<T, E>
  • Ok 与 Err
  • 使用 match 处理错误
  • unwrap() 与 expect()
  • unwrap_or() 与默认值
  • ? 运算符
  • 错误传播
  • Option 与 Result 的区别
  • parse() 错误处理
  • 文件读取错误处理
  • 自定义错误
  • 实战:配置解析与业务校验

《Rust Option 详解:安全处理“可能存在,也可能不存在”的值》 是转载文章,点击查看原文。


相关推荐


用腻了 WorkBuddy 的默认界面?这个开源小工具给它换上了毛玻璃
万少2026/7/18

用腻了 WorkBuddy 的默认界面?这个开源小工具给它换上了毛玻璃 天天用的界面,能不能好看一点? WorkBuddy 是一款很趁手的桌面工具,但默认界面看久了难免有点单调。作为一个既想要颜值、又不敢乱改软件的人,我做了一个小工具——workbuddy-skin,给它换上背景壁纸和毛玻璃质感的界面,而且全程不碰任何官方文件,随时能变回原样。 现在它已经打包成双击就能用的绿色程序,不用装环境、不用敲命令,普通用户也能轻松上手。 它做了什么 真背景层:在整个窗口铺一张


【AI智能体】Hermes Agent 从部署到项目实战操作详解
小码农叔叔2026/7/9

目录 一、前言 二、Hermes 介绍 2.1 Hermes 是什么 2.2 与普通AI聊天工具区别 2.3 Hermes 核心理念:自我进化 2.4 Hermes 核心功能 2.5 Hermes 与 OpenClaw 对比 2.6 哪些人适合使用Hermes 三、Hermes 桌面版安装过程 3.1 获取安装包 3.2 安装过程 四、Hermes 桌面版配置 4.1 界面基本功能介绍与使用 4.1.1 开启对话 4.1.2 对话框模型选择 4.1.3 技


GO语言变量与数据类型
知恒(Eternal)2026/7/1

Go语言变量与数据类型详解 本篇文章深入讲解Go语言的数据类型体系,包括基本类型、复合类型、类型转换和指针。理解数据类型是编程的基石,本篇将帮助你建立扎实的类型系统认知。 1. 变量基础 1.1 变量的本质 变量是程序运行时存储数据的容器。每个变量都有三个核心属性:名字、类型和值。名字用于在代码中引用变量,类型决定了变量能存储的数据种类和大小,值则是变量当前保存的具体数据。 在Go语言中,变量必须先声明后使用。这是Go语言设计理念的一部分,它要求程序员明确表达意图,减少错误。Go是一种静态类


系统架构设计师-数据库并发控制核心机制
上岸研究员2026/6/22

一、引言 1.1 核心概念定义 并发控制是数据库管理系统为保证多事务并发访问时数据一致性、完整性而实现的核心机制,事务是并发控制的基本逻辑单位,指对数据库进行读写操作的一组不可分割的执行序列。封锁协议是当前关系型数据库实现并发控制的主流技术手段,通过对数据对象施加不同类型的锁、控制锁的申请与释放时机,实现事务间的访问隔离。 1.2 软考考点定位 本知识点属于软考高级系统架构设计师考试大纲中 "数据库架构设计" 模块的核心内容,历年考试中选择题、案例分析题均有涉及,平均占比约 5-8 分,


2026年支持自定义模板的AI PPT工具测评:5款工具横向对比
一头爱吃肉的牛2026/6/14

市面上AI生成PPT的工具很多,但能上传自己公司模板的却没几个。如果你有统一的品牌规范,或者想保持PPT风格一致,这篇文章实测5款支持自定义模板的工具,帮你找到最适合的那一款。 声明:本文基于2026年5月对5款AI PPT工具的独立模拟测试,数据仅供参考,请以各工具官网最新信息为准。 一、为什么你需要这个功能? 你有没有遇到过这种情况:公司有统一的PPT模板,但你用AI工具生成后,发现字体不对、颜色不对、logo位置也不对,最后还得一页一页手动改? 自定义模板功能的价值在于:你可以


Re:Mysql数据库基础篇(一):CentOS/Linux 环境下的完整安装/运行/登录Mysql流程与首次登录异常处理
小此方2026/6/7

◆ 博主名称: 小此方-CSDN博客 大家好,欢迎来到小此方的博客。 ⭐️Linux系列个人专栏: 【主题曲】Mysql ⭐️此方的GitHub: github_此方 ⭐️ Re系列专栏:我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record) 文章目录 概要&序論一、下载准备工作1.1 查看和卸载你的机器上原有的Mysql1.1


金融系统数据一致性之战:联机交易与批量作业的冲突处理完全指南
无心水2026/6/1

金融系统数据一致性之战:联机交易与批量作业的冲突处理完全指南 关键词:金融系统 数据一致性 联机交易 批量作业 乐观锁 悲观锁 热点账户 分布式事务 死锁避免 阅读建议:本文约12000字,配有7张Mermaid流程图和2张对比表格,建议先收藏再阅读。 📑 目录 引言:一个夜间批量引发的血案冲突根源分析:联机与批量的“三座大山”六大核心策略详解 3.1 乐观锁:轻量级的“版本裁判”3.2 悲观锁:霸道的“行级门禁”3.3 物理/逻辑隔离:错峰与状态控制3.4 串行化处


本地4B开源模型,把任何App当Skil用!告 别token焦虑,私密性强~
AI袋鼠帝2026/5/10

​ 大家好,我是袋鼠帝。 上次给大家分享了一个CUA的开源项目,能让AI Agent直接操控电脑界面,相当于把任何App都变成Agent的Skill。反响还不错。 开源Turix,你可以把任何App当Agent Skill用!比如微信... 但评论区有两个比较多的反馈: 太耗token了。 截图上去,安全吗? 说实话,这两个问题,我自己用下来也发现了,GUI操作确实耗token。 模型要持续截屏、理解界面、定位元素、执行操作,每一步都在烧token。 特别是在全自动编程流程里,有数据表明,GU


Flutter for OpenHarmony 跨平台开发:记事本功能实战指南
liulian09162026/5/1

Flutter for OpenHarmony 跨平台开发:记事本功能实战指南 欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net 一、引言 记事本是移动设备中最基础且常用的应用之一,其开发涉及数据存储、列表展示、搜索过滤、状态管理等多个技术领域。随着鸿蒙生态的快速发展,如何高效地实现跨平台记事本应用,成为开发者关注的技术要点。 Flutter作为Google推出的开源UI框架,凭借其跨平台能力和丰富的组件生态,为记事本功能的实现提供


linux之进程优先与切换调度
feng_you_ying_li2026/4/22

1.进程优先优先级 是进程得到CPU的先后顺序,由于CPU少但进程多,因此要通过优先级确定谁先谁后的问题。 (1)优先级VS权限 优先级:得到资源的前提下获取资源的先后问题。 权限:能否得到某种资源的资格。 优先级在struct_task中是一个整形,其的值越低表明优先级越高,反之越低。 (2)基于时间片的分时操作系统 每一个进程依次占用CPU的时间是有限的,即时间片的时间到了该进程就只能等下一轮了。 OS在分配顺序时要考虑公平性,即进程的优先级可能会发生变化,但变化的幅度不能太

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读