Option 与空值处理
学习目标
- 理解 Rust 为什么没有 null
- 掌握
Option<T>的完整用法 - 掌握 Option 的组合子方法
- 学会用
?运算符简化代码
核心概念
为什么没有 null?
Tony Hoare 称 null 为"十亿美元错误"。Rust 用 Option<T> 替代:
// null 的问题
// let name: String = null;
// name.to_uppercase() // 运行时崩溃
// Rust 的方案
let name: Option<String> = None;
// name.unwrap() // 编译器强迫你处理 None 的情况
Option 的基本用法
fn find_student(id: u32) -> Option<String> {
match id {
1 => Some(String::from("Alice")),
2 => Some(String::from("Bob")),
_ => None,
}
}
fn main() {
match find_student(1) {
Some(name) => println!("找到: {}", name),
None => println!("未找到"),
}
}
组合子方法
fn main() {
let x: Option<i32> = Some(5);
let y: Option<i32> = None;
// map: 转换 Some 内的值
let a = x.map(|n| n * 2); // Some(10)
let b = y.map(|n| n * 2); // None
// and_then: 链式可能失败的操作
let c = x.and_then(|n| {
if n > 0 { Some(n.to_string()) } else { None }
}); // Some("5")
// filter: 条件过滤
let d = x.filter(|&n| n > 3); // Some(5)
let e = x.filter(|&n| n > 10); // None
// or / or_else: 提供默认值
let f = y.or(Some(42)); // Some(42)
let g = y.or_else(|| Some(99)); // Some(99)
// unwrap_or / unwrap_or_else
let h = y.unwrap_or(0); // 0
let i = y.unwrap_or_else(|| 100); // 100
}
链式调用
fn parse_header(raw: &str) -> Option<u32> {
raw.strip_prefix("0x")
.and_then(|hex| u32::from_str_radix(hex, 16).ok())
}
fn main() {
let headers = vec!["0xFF", "0x1A", "invalid", "0x0"];
for h in &headers {
println!("{} => {:?}", h, parse_header(h));
}
}
? 运算符
fn get_first_char(s: &str) -> Option<char> {
s.chars().next() // next() 返回 Option<char>
}
fn first_char_uppercase(s: &str) -> Option<char> {
let c = get_first_char(s)?; // None 时提前返回 None
Some(c.to_uppercase().next()?)
}
fn main() {
println!("{:?}", first_char_uppercase("hello")); // Some('H')
println!("{:?}", first_char_uppercase("")); // None
}
嵌套 Option
fn main() {
let x: Option<Option<i32>> = Some(Some(5));
// flatten: 展平嵌套
let y: Option<i32> = x.flatten(); // Some(5)
let z: Option<Option<i32>> = Some(None);
let w: Option<i32> = z.flatten(); // None
}
实践练习
练习 1:安全索引
fn safe_get(v: &[i32], index: usize) -> Option<i32> {
v.get(index).copied()
}
fn main() {
let data = [10, 20, 30, 40, 50];
match safe_get(&data, 2) {
Some(val) => println!("data[2] = {}", val),
None => println!("索引越界"),
}
match safe_get(&data, 10) {
Some(val) => println!("data[10] = {}", val),
None => println!("索引越界"),
}
}
练习 2:配置读取
struct Config {
host: Option<String>,
port: Option<u16>,
debug: Option<bool>,
}
impl Config {
fn host(&self) -> &str {
self.host.as_deref().unwrap_or("localhost")
}
fn port(&self) -> u16 {
self.port.unwrap_or(8080)
}
fn is_debug(&self) -> bool {
self.debug.unwrap_or(false)
}
}
fn main() {
let config = Config {
host: Some(String::from("0.0.0.0")),
port: None,
debug: Some(true),
};
println!("{}:{}", config.host(), config.port());
println!("debug: {}", config.is_debug());
}
练习 3:查找链
fn find_department(company: &str, employee: &str) -> Option<String> {
let departments = [
("acme", vec!["alice", "bob"]),
("globex", vec!["charlie", "dave"]),
];
for (dept, members) in &departments {
if *dept == company && members.contains(&employee) {
return Some(dept.to_string());
}
}
None
}
fn main() {
let result = find_department("acme", "alice")
.map(|d| format!("部门: {}", d))
.unwrap_or(String::from("未找到"));
println!("{}", result);
}
常见错误
1. 滥用 unwrap
let x: Option<i32> = None;
// x.unwrap(); // ❌ panic
// ✅ 用 unwrap_or 或 match
let value = x.unwrap_or(0);
2. ? 用在返回 () 的函数
// ❌ 函数返回 (),不能用 ?
fn main() {
let x: Option<i32> = Some(5);
let y = x?; // 错误
}
// ✅ 函数返回 Option
fn foo() -> Option<i32> {
let x: Option<i32> = Some(5);
let y = x?;
Some(y)
}
小结
| 方法 | 作用 |
|---|---|
unwrap() | 有值取出,None panic |
unwrap_or(default) | None 时用默认值 |
map(f) | 转换 Some 内的值 |
and_then(f) | 链式可能失败的操作 |
filter(pred) | 条件过滤 |
or(other) | None 时用备选 |
? | None 时提前返回 |