讲解

Rust 没有 null。这个价值数十亿美元的错误被枚举 Option 取代:Some(T) 表示有值,None 表示没有。关键点:Option 和 T 是不同的类型——手里有 Option 时编译器不允许你直接当 i32 用,必须先处理 None 的可能性才能拿到值。「可能不存在」从运行时空指针异常变成了编译期类型约束。

Result<T, E> 是同样的思想用于「可能失败」:Ok(T) 成功带结果,Err(E) 失败带原因。文件读取、网络请求、字符串解析这些会失败的操作,返回的都是 Result。和第 18 章的 panic 不同,Result 是给「预期内的、可恢复的失败」用的。

手工 match 之外,两者都有丰富的组合子让代码更顺:unwrap_or(default) 给默认值;map(f) 有值就变换;unwrap()/expect("原因") 直接取值、没有就 panic(仅在确定有值或示例代码中使用)。最重要的是 ? 运算符:在返回 Result(或 Option)的函数里,expr? 表示「失败就把错误原样返回给调用方,成功就取出值继续」——一行替代一整个 match,是实战代码的标准写法。

示例

Option 的三种打开方式:

fn first_even(numbers: &[i32]) -> Option<i32> {
    for &n in numbers {
        if n % 2 == 0 {
            return Some(n);
        }
    }
    None
}

fn main() {
    let found = first_even(&[1, 3, 4, 7]);
    assert_eq!(found, Some(4));

    // 1. 默认值兜底
    let v = first_even(&[1, 3, 5]).unwrap_or(-1);
    assert_eq!(v, -1);

    // 2. map:有值就变换,结果还是 Option
    let doubled = first_even(&[1, 4]).map(|n| n * 2);
    assert_eq!(doubled, Some(8));

    // 3. match:两个分支都处理
    match first_even(&[]) {
        Some(n) => println!("第一个偶数是 {n}"),
        None => println!("没有偶数"),
    }
}

Result 与 ? 运算符:

fn parse_port(text: &str) -> Result<u16, String> {
    match text.parse::<u16>() {
        Ok(n) if n > 0 => Ok(n),
        Ok(_) => Err(String::from("端口必须大于 0")),
        Err(_) => Err(format!("「{text}」不是合法端口")),
    }
}

// ?:Err 直接向上返回,Ok 取值继续
fn describe_service(port_text: &str) -> Result<String, String> {
    let port = parse_port(port_text)?;
    Ok(format!("服务运行在 {port} 端口"))
}

fn main() {
    assert_eq!(describe_service("8080"), Ok(String::from("服务运行在 8080 端口")));
    assert!(describe_service("abc").is_err());
    assert!(describe_service("0").is_err());

    match describe_service("443") {
        Ok(desc) => println!("{desc}"),
        Err(e) => println!("启动失败:{e}"),
    }
}

常见坑

  • unwrap 当常规取值:unwrap 遇到 None/Err 直接 panic。示例和测试里用没问题,业务代码优先 unwrap_or、match 或 ?。
  • expect 不写人话:expect("不可能失败") 在真的失败时毫无信息量;写清楚当时假设了什么,例如 expect("配置文件必须存在")。
  • 在 main 里用 ?:? 要求所在函数返回 Result 或 Option。main 想直接用 ?,可以写 fn main() -> Result<(), Box>。
  • 把 Option 当布尔判断后 unwrap:if opt.is_some() { opt.unwrap() } 是反模式,if let Some(v) = opt 一步到位且不会 panic。

小结

Option 取代 null、Result 建模可恢复失败;unwrap_or / map / match / ? 四件套覆盖日常;unwrap 和 expect 是确定有值时的快行道。下一章看两个最常用的集合:Vec 与 String。