概述

Rust 的错误处理以其可靠性和类型安全而闻名。与其他使用异常的语言不同,Rust 没有异常,而是使用 Result<T, E>Option<T> 类型来处理可恢复错误,使用 panic! 宏处理不可恢复错误。

不可恢复错误与 panic!

触发 panic!

fn main() {
    panic!("这是一个 panic!");
}

实际中的 panic! 示例

fn main() {
    let v = vec![1, 2, 3];
    
    v[99];  // panic!:索引越界
}

使用 backtrace

fn main() {
    let v = vec![1, 2, 3];
    
    v[99];  // panic!:索引越界
}

从 panic! 中恢复

use std::panic;

fn main() {
    let result = panic::catch_unwind(|| {
        panic!("test panic");
    });
    
    assert!(result.is_err());
    println!("Panic 被捕获了");
}

可恢复错误与 Result

Result 枚举定义

enum Result<T, E> {
    Ok(T),
    Err(E),
}

使用 Result

fn main() {
    use std::fs::File;
    
    let greeting_file_result = File::open("hello.txt");
    
    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => panic!("打开文件失败: {:?}", error),
    };
}

匹配不同的错误

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file_result = File::open("hello.txt");
    
    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("创建文件失败: {:?}", e),
            },
            other_error => {
                panic!("打开文件失败: {:?}", other_error)
            }
        },
    };
}

使用 unwrap_or_else

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("创建文件失败: {:?}", error);
            })
        } else {
            panic!("打开文件失败: {:?}", error);
        }
    });
}

错误传播

fn read_username_from_file() -> Result<String, std::io::Error> {
    let username_file_result = File::open("hello.txt");
    
    let mut username_file = match username_file_result {
        Ok(file) => file,
        Err(e) => return Err(e),
    };
    
    let mut username = String::new();
    
    match username_file.read_to_string(&mut username) {
        Ok(_) => Ok(username),
        Err(e) => Err(e),
    }
}

使用 ? 运算符传播错误

fn read_username_from_file() -> Result<String, std::io::Error> {
    let mut username_file = File::open("hello.txt")?;
    let mut username = String::new();
    username_file.read_to_string(&mut username)?;
    Ok(username)
}

链式调用 ? 运算符

fn read_username_from_file() -> Result<String, std::io::Error> {
    let mut username = String::new();
    File::open("hello.txt")?.read_to_string(&mut username)?;
    Ok(username)
}

使用 fs::read_to_string

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

fn read_username_from_file() -> Result<String, io::Error> {
    fs::read_to_string("hello.txt")
}

? 运算符只能用于返回 Result 的函数

fn main() {
    let greeting_file = File::open("hello.txt")?;
    // 错误:? 运算符只能在返回 Result 的函数中使用
}

panic! 还是返回 Result

fn main() {
    let greeting_file = File::open("hello.txt").unwrap();
    // 如果文件不存在,会 panic!
}
fn main() {
    let greeting_file = File::open("hello.txt")
        .expect("hello.txt 应该包含在项目中");
    // 如果文件不存在,会 panic! 并显示自定义错误消息
}

自定义错误类型

定义自定义错误

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

fn read_and_parse() -> Result<i32, MyError> {
    let content = std::fs::read_to_string("data.txt")
        .map_err(MyError::IoError)?;
    
    content.trim().parse::<i32>()
        .map_err(|_| MyError::ParseError("无法解析为整数".to_string()))
}

fn main() {
    match read_and_parse() {
        Ok(number) => println!("解析的数字: {}", number),
        Err(e) => println!("错误: {:?}", e),
    }
}

使用 thiserror 简化错误处理

# Cargo.toml
[dependencies]
thiserror = "1.0"
use thiserror::Error;

#[derive(Error, Debug)]
enum MyError {
    #[error("IO 错误: {0}")]
    IoError(#[from] std::io::Error),
    
    #[error("解析错误: {0}")]
    ParseError(String),
}

fn read_and_parse() -> Result<i32, MyError> {
    let content = std::fs::read_to_string("data.txt")?;
    
    content.trim().parse::<i32>()
        .map_err(|_| MyError::ParseError("无法解析为整数".to_string()))
}

fn main() {
    match read_and_parse() {
        Ok(number) => println!("解析的数字: {}", number),
        Err(e) => println!("错误: {}", e),
    }
}

使用 anyhow 简化错误处理

# Cargo.toml
[dependencies]
anyhow = "1.0"
use anyhow::{Result, Context};

fn read_and_parse() -> Result<i32> {
    let content = std::fs::read_to_string("data.txt")
        .context("读取文件失败")?;
    
    let number: i32 = content.trim().parse()
        .context("解析数字失败")?;
    
    Ok(number)
}

fn main() {
    match read_and_parse() {
        Ok(number) => println!("解析的数字: {}", number),
        Err(e) => println!("错误: {:?}", e),
    }
}

Option 与 Result 的转换

Option 转 Result

fn parse_number(input: Option<&str>) -> Result<i32, String> {
    input.ok_or("输入为空")?.parse::<i32>()
        .map_err(|e| format!("解析错误: {}", e))
}

fn main() {
    let result = parse_number(Some("42"));
    println!("解析结果: {:?}", result);
    
    let result = parse_number(None);
    println!("解析结果: {:?}", result);
}

Result 转 Option

fn get_first(matrix: Vec<Vec<i32>>) -> Option<i32> {
    matrix.get(0)?.get(0).copied()
}

fn main() {
    let matrix = vec![vec![1, 2], vec![3, 4]];
    println!("第一个元素: {:?}", get_first(matrix));
    
    let empty: Vec<Vec<i32>> = vec![];
    println!("空矩阵的第一个元素: {:?}", get_first(empty));
}

错误处理的最佳实践

1. 使用 Result 处理可恢复错误

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

fn main() {
    match divide(10.0, 0.0) {
        Ok(result) => println!("结果: {}", result),
        Err(e) => println!("错误: {}", e),
    }
}

2. 使用 panic! 处理不可恢复错误

fn verify_password(password: &str) {
    if password.is_empty() {
        panic!("密码不能为空");
    }
    
    // 验证密码的逻辑...
}

fn main() {
    verify_password("password123");
    // verify_password("");  // 会 panic!
}

3. 使用 unwrap 和 expect 谨慎

fn main() {
    // 只在你确定不会失败的情况下使用 unwrap
    let result = Some(42).unwrap();
    
    // 使用 expect 提供更好的错误消息
    let result = Some(42).expect("应该有值");
}

4. 错误上下文

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

fn read_config() -> Result<String, io::Error> {
    fs::read_to_string("config.toml")
        .map_err(|e| {
            io::Error::new(
                e.kind(),
                format!("无法读取配置文件: {}", e)
            )
        })
}

fn main() {
    match read_config() {
        Ok(config) => println!("配置: {}", config),
        Err(e) => println!("错误: {}", e),
    }
}

实际应用示例

文件处理

use std::fs::File;
use std::io::{self, BufRead, BufReader};

fn read_lines(filename: &str) -> io::Result<Vec<String>> {
    let file = File::open(filename)?;
    let reader = BufReader::new(file);
    
    reader.lines().collect()
}

fn main() {
    match read_lines("example.txt") {
        Ok(lines) => {
            for line in lines {
                println!("{}", line);
            }
        }
        Err(e) => {
            eprintln!("读取文件失败: {}", e);
        }
    }
}

网络请求

use std::io;
use std::net::TcpStream;

fn connect_to_server(host: &str, port: u16) -> io::Result<TcpStream> {
    let address = format!("{}:{}", host, port);
    TcpStream::connect(&address)
}

fn main() {
    match connect_to_server("example.com", 80) {
        Ok(stream) => {
            println!("成功连接到服务器");
            // 使用 stream...
        }
        Err(e) => {
            eprintln!("连接失败: {}", e);
        }
    }
}

配置解析

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

#[derive(Debug)]
struct Config {
    host: String,
    port: u16,
}

impl Config {
    fn from_env() -> Result<Self, io::Error> {
        let host = env::var("HOST")
            .unwrap_or_else(|_| "localhost".to_string());
        
        let port = env::var("PORT")
            .unwrap_or_else(|_| "8080".to_string())
            .parse::<u16>()
            .map_err(|_| io::Error::new(
                io::ErrorKind::InvalidInput,
                "无效的端口号"
            ))?;
        
        Ok(Config { host, port })
    }
}

fn main() {
    match Config::from_env() {
        Ok(config) => println!("配置: {:?}", config),
        Err(e) => eprintln!("配置错误: {}", e),
    }
}

总结

本教程详细介绍了 Rust 的错误处理机制:

  1. 不可恢复错误

    • 使用 panic!
    • 程序会终止
    • 适用于无法恢复的错误
  2. 可恢复错误

    • 使用 Result<T, E> 类型
    • 可以处理和传播错误
    • 使用 ? 运算符简化错误传播
  3. 错误处理方法

    • unwrap()expect()
    • unwrap_or()unwrap_or_else()
    • match 表达式
    • ? 运算符
  4. 自定义错误类型

    • 定义自己的错误枚举
    • 使用 thiserror 简化错误定义
    • 使用 anyhow 简化错误处理
  5. 最佳实践

    • 使用 Result 处理可恢复错误
    • 使用 panic! 处理不可恢复错误
    • 提供有意义的错误消息
    • 使用错误上下文

Rust 的错误处理系统强制你显式处理所有可能的错误,这虽然增加了一些初始的编码工作量,但大大提高了代码的可靠性和安全性。

下一步

在下一教程中,我们将学习 Rust 的模块与包管理,包括:

  • 模块系统

  • 包和 crate

  • 使用外部包

  • 工作空间

继续学习 Rust,掌握这门强大语言的更多特性!