Rust 错误处理:unwrap() 与 ok_or_else() 的对比


本文对比 Rust 中两个常用的错误处理方法——unwrap()ok_or_else(),帮助你选择合适的错误处理方式。

引:基础概念

// Option<T>:表示值可能存在或不存在
enum Option<T> {
    Some(T),
    None,
}

// Result<T, E>:表示操作可能成功或失败
enum Result<T, E> {
    Ok(T),
    Err(E),
}

一、unwrap():直接但危险

1.方法签名

// Option<T> 上的 unwrap()
impl<T> Option<T> {
    pub fn unwrap(self) -> T {
        match self {
            Some(val) => val,
            None => panic!("called `Option::unwrap()` on a `None` value"),
        }
    }
}

// Result<T, E> 上的 unwrap()
impl<T, E> Result<T, E> {
    pub fn unwrap(self) -> T {
        match self {
            Ok(val) => val,
            Err(err) => panic!("called `Result::unwrap()` on an `Err` value: {:?}", err),
        }
    }
}

2.使用示例

fn divide(a: i32, b: i32) -> Option<i32> {
    if b == 0 { None } else { Some(a / b) }
}

let result = divide(10, 2).unwrap(); // 返回 5
let result = divide(10, 0).unwrap(); // panic

3.何时使用

测试代码

#[test]
fn test_calculation() {
    let result = calculate(10, 5);
    assert_eq!(result.unwrap(), 2);
}

逻辑上不可能失败的情况

fn get_first_char(s: &str) -> char {
    s.chars().next().unwrap() // 字符串非空时安全
}

4.危险之处

// 生产环境中的 unwrap
fn process_user_input(input: &str) -> i32 {
    input.parse::<i32>().unwrap() // 输入非数字时程序崩溃
}

问题

  • 用户输入不可控,会导致程序崩溃
  • 没有提供有意义的错误信息
  • 无法优雅地恢复或处理错误

二、ok_or_else():优雅的错误转换

1.方法签名

impl<T> Option<T> {
    pub fn ok_or_else<E, F>(self, err: F) -> Result<T, E>
    where
        F: FnOnce() -> E,
    {
        match self {
            Some(val) => Ok(val),
            None => Err(err()),
        }
    }
}

关键特性

  • Option<T> 转换为 Result<T, E>
  • 错误值通过闭包延迟计算(仅在需要时才创建)
  • 返回 Result 类型,可以与 ? 操作符配合使用

2.基本使用

let some_value = Some(42);
let result: Result<i32, &str> = some_value.ok_or_else(|| "value not found");
// 结果:Ok(42)

let none_value: Option<i32> = None;
let result: Result<i32, &str> = none_value.ok_or_else(|| "value not found");
// 结果:Err("value not found")

3.延迟计算优势

fn expensive_error_creation() -> String {
    println!("Creating expensive error message...");
    "This is an expensive error message".to_string()
}

let some_value = Some(42);
let result = some_value.ok_or_else(expensive_error_creation);
// 不会打印 "Creating expensive error message..."

let none_value: Option<i32> = None;
let result = none_value.ok_or_else(expensive_error_creation);
// 会打印 "Creating expensive error message..."

对比 ok_or()

// ok_or() 会立即计算错误值
let none_value: Option<i32> = None;
let result = none_value.ok_or(expensive_error_creation());
// 无论是否需要,都会调用 expensive_error_creation()

4.实际应用

use web_sys::Window;

#[derive(Debug)]
pub enum PlayerError {
    PlatformError(String),
    AudioError(String),
}

type PlayerResult<T> = Result<T, PlayerError>;

pub fn init_audio_player() -> PlayerResult<Window> {
    let window = web_sys::window()
        .ok_or_else(|| PlayerError::PlatformError(
            "Failed to get window object".to_string()
        ))?;

    Ok(window)
}

三、相关方法对比

1.方法对比表

方法 适用类型 返回类型 错误处理 延迟计算 使用场景
unwrap() Option, Result T panic N/A 测试、原型开发
expect(msg) Option, Result T panic with message N/A 需要自定义 panic 信息
ok_or(err) Option Result<T, E> 转换为 Result 错误值简单且可预计算
ok_or_else(f) Option Result<T, E> 转换为 Result 错误值计算昂贵或复杂
map_err(f) Result Result<T, E2> 转换错误类型 需要转换错误类型

2.expect():提供更好的 panic 信息

// unwrap() 的 panic 信息不够详细
let value = None.unwrap();
// panic: called `Option::unwrap()` on a `None` value

// expect() 可以提供自定义信息
let value = None.expect("Failed to load configuration file");
// panic: Failed to load configuration file

3.map_err():转换错误类型

use std::num::ParseIntError;

#[derive(Debug)]
enum AppError {
    ParseError(String),
}

fn parse_number(input: &str) -> Result<i32, AppError> {
    input.parse::<i32>()
        .map_err(|e| AppError::ParseError(format!("Failed to parse '{}': {}", input, e)))
}

四、实际应用场景

1.配置文件加载

use serde::Deserialize;
use std::fs;

#[derive(Debug, Deserialize)]
struct AppConfig {
    database_url: String,
    max_connections: u32,
}

#[derive(Debug)]
enum ConfigError {
    FileNotFound(String),
    ParseError(String),
}

fn load_config(path: &str) -> Result<AppConfig, ConfigError> {
    let content = fs::read_to_string(path)
        .map_err(|e| ConfigError::FileNotFound(
            format!("Cannot read config file: {}", e)
        ))?;

    let config: AppConfig = serde_json::from_str(&content)
        .map_err(|e| ConfigError::ParseError(
            format!("Invalid JSON in config: {}", e)
        ))?;

    Ok(config)
}

2.数据库查询

use sqlx::PgPool;

#[derive(Debug)]
enum DbError {
    QueryError(String),
    NotFound(String),
}

async fn get_user_by_email(pool: &PgPool, email: &str) -> Result<User, DbError> {
    let user = sqlx::query_as::<_, User>(
        "SELECT id, name, email FROM users WHERE email = $1"
    )
    .bind(email)
    .fetch_optional(pool)
    .await
    .map_err(|e| DbError::QueryError(
        format!("Database query failed: {}", e)
    ))?
    .ok_or_else(|| DbError::NotFound(
        format!("User with email '{}' not found", email)
    ))?;

    Ok(user)
}

五、最佳实践

1.推荐做法

生产环境使用 ok_or_else()

fn process_input(input: &str) -> Result<i32, AppError> {
    let number = input.parse::<i32>()
        .map_err(|_| AppError::InvalidInput(input.to_string()))?;
    Ok(number)
}

提供有意义的错误信息

let window = web_sys::window()
    .ok_or_else(|| PlayerError::PlatformError(
        "Failed to access browser window"
    ))?;

在测试代码中使用 unwrap()

#[test]
fn test_user_creation() {
    let user = User::new("test@example.com").unwrap();
    assert_eq!(user.email, "test@example.com");
}

2.避免做法

生产环境中滥用 unwrap()

// 危险!用户输入可能导致 panic
fn process_user_input(input: &str) -> i32 {
    input.parse::<i32>().unwrap()
}

使用空的错误信息

// 不好的做法
let value = option.ok_or_else(|| "")?;

// 好的做法
let value = option.ok_or_else(|| {
    AppError::NotFound(format!("Value not found: {:?}", context))
})?;

六、常见陷阱

1.忘记处理 Option

// 危险:直接使用可能为 None 的值
fn get_user_name(user_id: u32) -> String {
    let user = users.get(&user_id);
    user.name // 编译错误!
}

// 正确做法
fn get_user_name(user_id: u32) -> Option<String> {
    users.get(&user_id).map(|user| user.name.clone())
}

2.过度使用 unwrap()

// 危险:多个 unwrap() 增加崩溃风险
fn process_data(input: &str) -> i32 {
    let parsed = input.parse::<i32>().unwrap();
    let doubled = parsed * 2;
    let result = calculate(doubled).unwrap();
    result
}

// 正确做法
fn process_data(input: &str) -> Result<i32, AppError> {
    let parsed = input.parse::<i32>()
        .map_err(|_| AppError::InvalidInput(input.to_string()))?;
    let doubled = parsed * 2;
    let result = calculate(doubled)
        .map_err(|e| AppError::CalculationError(e.to_string()))?;
    Ok(result)
}

3.错误信息不够详细

// 不好的做法
let value = option.ok_or_else(|| "error")?;

// 好的做法
let value = option.ok_or_else(|| {
    AppError::NotFound {
        resource: "user".to_string(),
        id: user_id,
    }
})?;

4.忽略错误传播

// 不好的做法:静默忽略错误
fn process_file(path: &str) {
    let _ = std::fs::read_to_string(path);
}

// 好的做法:处理或传播错误
fn process_file(path: &str) -> Result<String, AppError> {
    let content = std::fs::read_to_string(path)
        .map_err(|e| AppError::IoError(format!("Failed to read file: {}", e)))?;
    Ok(content)
}

七、总结

1.核心要点

unwrap()

  • 简单直接,但会导致 panic
  • 适用于测试代码和逻辑上不可能失败的情况
  • 生产环境中应谨慎使用

ok_or_else()

  • Option<T> 转换为 Result<T, E>
  • 错误值延迟计算,性能更优
  • 提供有意义的错误信息,适合生产环境
  • 提供更安全、更灵活的错误处理

2.选择建议

场景 推荐方法
测试代码 unwrap()
原型开发 unwrap()expect()
逻辑上不可能失败 unwrap()expect()
生产环境 ok_or_else()
需要错误传播 ok_or_else() + ?
错误值计算昂贵 ok_or_else()

结论:当错误创建成本较高且 OptionSome 的概率较高时,ok_or_else 能显著减少不必要的计算开销

3.最佳实践

  1. 生产环境优先使用 ok_or_else()
  2. 提供有意义的错误信息
  3. 使用自定义错误类型统一处理
  4. 测试代码可以使用 unwrap()
  5. 避免在生产环境中滥用 unwrap()

文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录