阶段四:错误处理

109 阅读3分钟

核心概念

  1. Result<T, E> 类型

    • 用于可能失败的操作,包含两个变体:
      enum Result<T, E> {
          Ok(T),  // 成功时携带结果
          Err(E), // 失败时携带错误信息
      }
      
    • 错误处理原则:不滥用 panic!,优先返回 Result 让调用者处理。
  2. Option<T> 类型

    • 用于可能缺失的值,包含两个变体:
      enum Option<T> {
          Some(T),
          None,
      }
      
    • 适用场景:查找数据不存在(如哈希表查询),而非错误处理。
  3. ? 运算符

    • 简化错误传播:若结果是 Err,则提前返回;否则解包 Ok 的值。
    • 只能在返回 ResultOption 的函数中使用。
  4. 自定义错误类型

    • 实现 std::error::Error trait,与 Result 结合使用。

学习步骤

1. 基础错误处理

// 从文件读取内容,返回 Result
use std::fs::File;
use std::io::Read;

fn read_file(path: &str) -> Result<String, std::io::Error> {
    let mut file = File::open(path)?; // 若打开失败,直接返回Err
    let mut content = String::new();
    file.read_to_string(&mut content)?; // 若读取失败,返回Err
    Ok(content)
}

2. 组合 OptionResult

// 解析字符串为整数,处理可能的格式错误
fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
    s.parse::<i32>()
}

fn main() {
    let num_str = "42";
    match parse_number(num_str) {
        Ok(n) => println!("解析成功: {}", n),
        Err(e) => println!("解析失败: {}", e),
    }
}

3. 自定义错误类型

use std::fmt;

// 自定义错误类型
#[derive(Debug)]
struct MyError {
    message: String,
}

// 实现 Display 用于打印
impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "自定义错误: {}", self.message)
    }
}

// 实现 Error trait
impl std::error::Error for MyError {}

// 返回自定义错误的函数
fn check_value(value: i32) -> Result<(), MyError> {
    if value < 0 {
        Err(MyError {
            message: format!("值 {} 不能为负数", value),
        })
    } else {
        Ok(())
    }
}

练习题

题目1:读取文件并处理错误

use std::fs::File;
use std::io::{self, Read};

fn read_file_contents(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn main() {
    let path = "hello.txt";
    match read_file_contents(path) {
        Ok(text) => println!("文件内容:\n{}", text),
        Err(e) => println!("读取文件失败: {}", e),
    }
}

题目2:解析整数并返回 Result

fn parse_integer(input: &str) -> Result<i32, std::num::ParseIntError> {
    input.parse::<i32>()
}

fn main() {
    let inputs = ["42", "abc", "100"];
    for s in inputs.iter() {
        match parse_integer(s) {
            Ok(n) => println!("解析成功: {} -> {}", s, n),
            Err(e) => println!("解析失败: {} -> {}", s, e),
        }
    }
}

题目3:自定义错误整合

#[derive(Debug)]
enum MyError {
    NegativeValue(i32),
    TooLarge(i32),
}

impl std::error::Error for MyError {}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            MyError::NegativeValue(v) => write!(f, "值 {} 不能为负数", v),
            MyError::TooLarge(v) => write!(f, "值 {} 超过最大值100", v),
        }
    }
}

fn validate_value(value: i32) -> Result<(), MyError> {
    if value < 0 {
        Err(MyError::NegativeValue(value))
    } else if value > 100 {
        Err(MyError::TooLarge(value))
    } else {
        Ok(())
    }
}

fn main() {
    let values = vec![-5, 50, 150];
    for v in values {
        match validate_value(v) {
            Ok(()) => println!("值 {} 有效", v),
            Err(e) => println!("错误: {}", e),
        }
    }
}

关键技巧

  1. 何时用 Result vs Option

    • Result:操作可能失败,需报告错误原因(如文件不存在)。
    • Option:数据可能不存在,不涉及错误(如查找键不存在)。
  2. 避免 unwrap()expect()

    • 生产代码中尽量用 match? 处理错误,避免程序崩溃。
    • unwrap() 会导致 panic!,仅适用于原型快速开发。
  3. 错误类型转换

    • 使用 map_err 将错误类型转换为自定义错误:
      File::open("file.txt").map_err(|e| MyError::IoError(e))?;
      

常见错误分析

错误示例:滥用 unwrap()

let num = "abc".parse::<i32>().unwrap(); // 运行时 panic!
  • 修复:改用 match? 处理可能的 Err

错误示例:忽略 Result

let _ = File::open("file.txt"); // 未处理可能的错误
  • 修复:至少用 if let 检查结果:
    if let Ok(file) = File::open("file.txt") {
        // 处理文件
    }
    

下一步任务

  1. 完成练习题,尝试在自定义错误中添加更多变体(如 FileReadError)。
  2. 阅读官方文档:错误处理