14·复合类型进阶

Result 与错误处理

Result 与错误处理

学习目标

  1. 理解 Result<T, E> 的设计
  2. 掌握 ? 运算符
  3. 学会 unwrapexpect 的使用场景
  4. 理解 From trait 实现错误转换

核心概念

Result 定义

enum Result<T, E> {
    Ok(T),   // 成功,包含值
    Err(E),  // 失败,包含错误
}

基本用法

use std::num::ParseIntError;

fn parse_number(s: &str) -> Result<i32, ParseIntError> {
    s.parse::<i32>()
}

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

    match parse_number("abc") {
        Ok(n) => println!("解析成功: {}", n),
        Err(e) => println!("解析失败: {}", e),
    }
}

? 运算符

use std::fs;
use std::io;

fn read_username() -> Result<String, io::Error> {
    let content = fs::read_to_string("username.txt")?;  // 失败时提前返回 Err
    Ok(content.trim().to_string())
}

// 等价于
fn read_username_verbose() -> Result<String, io::Error> {
    let content = match fs::read_to_string("username.txt") {
        Ok(c) => c,
        Err(e) => return Err(e),  // 提前返回
    };
    Ok(content.trim().to_string())
}

链式 ? 调用

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

fn read_config() -> Result<String, io::Error> {
    let mut file = File::open("config.txt")?;  // 可能失败
    let mut content = String::new();
    file.read_to_string(&mut content)?;          // 可能失败
    Ok(content)
}

自定义错误类型

use std::fmt;
use std::num::ParseIntError;

#[derive(Debug)]
enum AppError {
    ParseError(ParseIntError),
    NotFound(String),
    IoError(std::io::Error),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AppError::ParseError(e) => write!(f, "解析错误: {}", e),
            AppError::NotFound(name) => write!(f, "未找到: {}", name),
            AppError::IoError(e) => write!(f, "IO 错误: {}", e),
        }
    }
}

// 实现 From,让 ? 自动转换错误类型
impl From<ParseIntError> for AppError {
    fn from(e: ParseIntError) -> Self {
        AppError::ParseError(e)
    }
}

impl From<std::io::Error> for AppError {
    fn from(e: std::io::Error) -> Self {
        AppError::IoError(e)
    }
}

fn process() -> Result<i32, AppError> {
    let content = std::fs::read_to_string("data.txt")?;  // io::Error → AppError
    let number: i32 = content.trim().parse()?;            // ParseIntError → AppError
    Ok(number * 2)
}

Result 的组合子

fn main() {
    let ok: Result<i32, String> = Ok(5);
    let err: Result<i32, String> = Err(String::from("失败"));

    // map: 转换 Ok 值
    let a = ok.map(|n| n * 2);          // Ok(10)

    // map_err: 转换 Err 值
    let b = err.map_err(|e| e.to_uppercase());

    // and_then: 链式操作
    let c = ok.and_then(|n| {
        if n > 0 { Ok(n.to_string()) } else { Err(String::from("非正数")) }
    });

    // unwrap_or: 提供默认值
    let d = err.unwrap_or(0);           // 0

    // or_else: 提供备选
    let e = err.or_else(|_| Ok(42));    // Ok(42)
}

unwrap 和 expect

fn main() {
    let ok: Result<i32, &str> = Ok(42);

    // unwrap: Ok 则取出值,Err 则 panic
    let x = ok.unwrap();  // 42

    // expect: 同上,但 panic 时有自定义消息
    let y = ok.expect("应该成功");

    // 使用场景:
    // 1. 测试代码中
    // 2. 你能 100% 确定不会失败时
    // 3. 原型开发阶段
}

多种错误类型的处理

use std::fs;
use std::num::ParseIntError;

// 方式一:用 Box<dyn Error>(简单场景)
fn read_number() -> Result<i32, Box<dyn std::error::Error>> {
    let content = fs::read_to_string("number.txt")?;
    let number = content.trim().parse::<i32>()?;
    Ok(number)
}

// 方式二:用自定义枚举(正式项目)
enum MyError {
    Io(std::io::Error),
    Parse(ParseIntError),
}

impl From<std::io::Error> for MyError {
    fn from(e: std::io::Error) -> Self { MyError::Io(e) }
}

impl From<ParseIntError> for MyError {
    fn from(e: ParseIntError) -> Self { MyError::Parse(e) }
}

实践练习

练习 1:安全除法

fn safe_divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err(String::from("除数不能为零"))
    } else {
        Ok(a / b)
    }
}

fn main() {
    let cases = [(10.0, 3.0), (5.0, 0.0), (100.0, 7.0)];
    for (a, b) in &cases {
        match safe_divide(*a, *b) {
            Ok(result) => println!("{} / {} = {:.2}", a, b, result),
            Err(e) => println!("错误: {}", e),
        }
    }
}

练习 2:配置文件解析

use std::collections::HashMap;

#[derive(Debug)]
enum ConfigError {
    MissingField(String),
    InvalidValue(String),
}

fn parse_config(input: &str) -> Result<HashMap<String, String>, ConfigError> {
    let mut config = HashMap::new();
    for line in input.lines() {
        let line = line.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        let parts: Vec<&str> = line.splitn(2, '=').collect();
        if parts.len() != 2 {
            return Err(ConfigError::InvalidValue(line.to_string()));
        }
        config.insert(parts[0].trim().to_string(), parts[1].trim().to_string());
    }
    Ok(config)
}

fn main() {
    let input = "host=localhost\nport=8080\n# 这是注释\nname=myapp";
    match parse_config(input) {
        Ok(config) => println!("{:?}", config),
        Err(e) => println!("解析失败: {:?}", e),
    }
}

练习 3:链式错误处理

use std::fs;
use std::num::ParseIntError;

fn sum_file_numbers(path: &str) -> Result<i64, Box<dyn std::error::Error>> {
    let content = fs::read_to_string(path)?;
    let mut sum = 0i64;
    for line in content.lines() {
        let num: i64 = line.trim().parse()?;
        sum += num;
    }
    Ok(sum)
}

fn main() {
    match sum_file_numbers("numbers.txt") {
        Ok(sum) => println!("总和: {}", sum),
        Err(e) => println!("错误: {}", e),
    }
}

常见错误

1. 忘记处理错误

fn risky() -> Result<i32, String> { Ok(42) }

fn main() {
    risky();  // ⚠️ 返回值被忽略

    // ✅ 显式处理
    match risky() {
        Ok(v) => println!("{}", v),
        Err(e) => eprintln!("{}", e),
    }
}

2. ? 用在返回 Result 的 main

// ❌ 默认 main 返回 ()
// fn main() {
//     let content = std::fs::read_to_string("file.txt")?;
// }

// ✅ main 返回 Result
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let content = std::fs::read_to_string("file.txt")?;
    println!("{}", content);
    Ok(())
}

小结

概念说明
Ok(T)成功值
Err(E)错误值
?成功继续,失败提前返回
map转换 Ok 值
and_then链式可能失败的操作
From实现错误类型自动转换
Box<dyn Error>通用错误类型

练习编辑器

rust
Loading...