13·复合类型入门

Option 与空值处理

Option 与空值处理

学习目标

  1. 理解 Rust 为什么没有 null
  2. 掌握 Option<T> 的完整用法
  3. 掌握 Option 的组合子方法
  4. 学会用 ? 运算符简化代码

核心概念

为什么没有 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 时提前返回

练习编辑器

rust
Loading...