名称: rust-error-handling 用户可调用: false 描述: 当使用Rust进行错误处理时,涉及Result、Option、自定义错误、thiserror和anyhow库时使用。适用于Rust应用程序中的错误处理。 允许工具:
- Bash
- Read
Rust错误处理
掌握Rust的错误处理机制,使用Result、Option、自定义错误类型和流行错误处理库,构建健壮的应用程序。
Result和Option
Result类型用于可恢复错误:
// Result<T, E>用于可能失败的操作
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("除零错误"))
} else {
Ok(a / b)
}
}
fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("结果: {}", result),
Err(e) => println!("错误: {}", e),
}
}
Option类型用于可选值:
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("Alice"))
} else {
None
}
}
fn main() {
match find_user(1) {
Some(name) => println!("找到: {}", name),
None => println!("用户未找到"),
}
}
使用?进行错误传播
使用?运算符:
use std::fs::File;
use std::io::{self, Read};
fn read_file(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 read_file_explicit(path: &str) -> Result<String, io::Error> {
let mut file = match File::open(path) {
Ok(f) => f,
Err(e) => return Err(e),
};
let mut contents = String::new();
match file.read_to_string(&mut contents) {
Ok(_) => Ok(contents),
Err(e) => Err(e),
}
}
?与Option一起使用:
fn get_first_char(text: &str) -> Option<char> {
text.chars().next()
}
fn process_text(text: Option<&str>) -> Option<char> {
let t = text?; // 如果text为None则返回None
get_first_char(t)
}
自定义错误类型
简单自定义错误:
use std::fmt;
#[derive(Debug)]
struct ParseError {
message: String,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "解析错误: {}", self.message)
}
}
impl std::error::Error for ParseError {}
fn parse_number(s: &str) -> Result<i32, ParseError> {
s.parse().map_err(|_| ParseError {
message: format!("无法解析 '{}'", s),
})
}
基于枚举的错误类型:
use std::fmt;
use std::io;
#[derive(Debug)]
enum AppError {
Io(io::Error),
Parse(String),
NotFound(String),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::Io(e) => write!(f, "IO错误: {}", e),
AppError::Parse(msg) => write!(f, "解析错误: {}", msg),
AppError::NotFound(item) => write!(f, "未找到: {}", item),
}
}
}
impl std::error::Error for AppError {}
impl From<io::Error> for AppError {
fn from(error: io::Error) -> Self {
AppError::Io(error)
}
}
fn process_file(path: &str) -> Result<String, AppError> {
let content = std::fs::read_to_string(path)?; // io::Error自动转换
if content.is_empty() {
Err(AppError::NotFound(path.to_string()))
} else {
Ok(content)
}
}
thiserror库
安装thiserror:
cargo add thiserror
使用thiserror定义自定义错误:
use thiserror::Error;
#[derive(Error, Debug)]
enum DataError {
#[error("IO错误: {0}")]
Io(#[from] std::io::Error),
#[error("解析错误: {0}")]
Parse(String),
#[error("验证失败: {field} 无效")]
Validation { field: String },
#[error("未找到: {0}")]
NotFound(String),
}
fn validate_user(name: &str) -> Result<(), DataError> {
if name.is_empty() {
return Err(DataError::Validation {
field: "name".to_string(),
});
}
Ok(())
}
fn load_data(path: &str) -> Result<String, DataError> {
let data = std::fs::read_to_string(path)?; // 自动转换io::Error
if data.is_empty() {
return Err(DataError::NotFound(path.to_string()));
}
Ok(data)
}
thiserror与源错误:
use thiserror::Error;
use std::io;
#[derive(Error, Debug)]
enum ConfigError {
#[error("读取配置文件失败")]
ReadError {
#[source]
source: io::Error,
},
#[error("配置格式无效")]
ParseError {
#[source]
source: serde_json::Error,
},
}
anyhow库
安装anyhow:
cargo add anyhow
使用anyhow处理应用程序错误:
use anyhow::{Result, Context, anyhow, bail};
fn read_config(path: &str) -> Result<String> {
let content = std::fs::read_to_string(path)
.context("读取配置文件失败")?;
if content.is_empty() {
bail!("配置文件为空");
}
Ok(content)
}
fn process_data(value: i32) -> Result<i32> {
if value < 0 {
return Err(anyhow!("值必须为正数, 得到 {}", value));
}
Ok(value * 2)
}
fn main() -> Result<()> {
let config = read_config("config.toml")
.context("加载配置失败")?;
let value = process_data(42)?;
println!("值: {}", value);
Ok(())
}
anyhow上下文链:
use anyhow::{Result, Context};
fn load_user(id: u32) -> Result<String> {
fetch_from_database(id)
.context("数据库查询失败")?
.parse()
.context(format!("解析用户 {} 失败", id))
}
fn fetch_from_database(id: u32) -> Result<String> {
// 实现
Ok(format!("user_{}", id))
}
错误转换
在错误类型之间转换:
use std::io;
use std::num::ParseIntError;
enum AppError {
Io(io::Error),
Parse(ParseIntError),
}
impl From<io::Error> for AppError {
fn from(error: io::Error) -> Self {
AppError::Io(error)
}
}
impl From<ParseIntError> for AppError {
fn from(error: ParseIntError) -> Self {
AppError::Parse(error)
}
}
fn process() -> Result<i32, AppError> {
let content = std::fs::read_to_string("file.txt")?;
let number: i32 = content.trim().parse()?;
Ok(number)
}
unwrap和expect
何时使用unwrap和expect:
fn unwrap_examples() {
// unwrap: 用通用消息恐慌
let value = Some(42).unwrap();
// expect: 用自定义消息恐慌
let value = Some(42).expect("值应存在");
// 仅在以下情况使用:
// 1. 测试
// 2. 原型
// 3. 确信不会恐慌时
// 更好: 处理错误
if let Some(value) = get_value() {
println!("{}", value);
}
}
fn get_value() -> Option<i32> {
Some(42)
}
Result组合器
使用Result方法:
fn combinators() -> Result<i32, String> {
// map: 转换Ok值
let result = Ok(5).map(|x| x * 2); // Ok(10)
// map_err: 转换Err值
let result = Err("错误").map_err(|e| format!("错误: {}", e));
// and_then (flatMap): 链式操作
let result = Ok(5)
.and_then(|x| Ok(x * 2))
.and_then(|x| Ok(x + 1)); // Ok(11)
// or_else: 提供错误时的替代
let result = Err("错误")
.or_else(|_| Ok(42)); // Ok(42)
// unwrap_or: 提供错误时的默认值
let value = Err("错误").unwrap_or(42); // 42
// unwrap_or_else: 计算错误时的默认值
let value = Err("错误").unwrap_or_else(|_| 42); // 42
Ok(value)
}
Option组合器
使用Option方法:
fn option_combinators() {
// map: 转换Some值
let result = Some(5).map(|x| x * 2); // Some(10)
// and_then (flatMap): 链式操作
let result = Some(5)
.and_then(|x| Some(x * 2))
.and_then(|x| Some(x + 1)); // Some(11)
// or: 提供替代
let result = None.or(Some(42)); // Some(42)
// unwrap_or: 提供默认值
let value = None.unwrap_or(42); // 42
// filter: 仅当谓词为真时保留
let result = Some(5).filter(|x| x > &3); // Some(5)
let result = Some(2).filter(|x| x > &3); // None
// ok_or: 将Option转换为Result
let result: Result<i32, &str> = Some(5).ok_or("错误"); // Ok(5)
}
模式匹配
使用match进行全面的错误处理:
use std::fs::File;
use std::io::ErrorKind;
fn open_file(path: &str) -> File {
let file = match File::open(path) {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => {
match File::create(path) {
Ok(file) => file,
Err(e) => panic!("创建文件失败: {:?}", e),
}
}
ErrorKind::PermissionDenied => {
panic!("权限被拒绝: {}", path);
}
other_error => {
panic!("打开文件失败: {:?}", other_error);
}
},
};
file
}
if let处理简单情况:
fn simple_match(result: Result<i32, String>) {
// 仅处理成功情况
if let Ok(value) = result {
println!("得到值: {}", value);
}
// 仅处理错误情况
if let Err(e) = result {
eprintln!("错误: {}", e);
}
}
Panic与Result
何时使用panic:
// 用于不可恢复错误或bug
fn get_element(index: usize) -> i32 {
let data = vec![1, 2, 3];
// 如果索引越界则恐慌(程序员错误)
data[index]
}
// 使用Result处理预期错误
fn safe_get_element(index: usize) -> Option<i32> {
let data = vec![1, 2, 3];
data.get(index).copied()
}
// 自定义恐慌消息
fn validate_config(value: i32) {
if value < 0 {
panic!("配置值必须为正数, 得到 {}", value);
}
}
// 条件恐慌
fn debug_only_panic(condition: bool) {
debug_assert!(condition, "这仅在调试构建中恐慌");
}
测试中的错误处理
测试错误条件:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_success() {
let result = divide(10.0, 2.0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 5.0);
}
#[test]
fn test_error() {
let result = divide(10.0, 0.0);
assert!(result.is_err());
}
#[test]
#[should_panic(expected = "Division by zero")]
fn test_panic() {
panic!("Division by zero");
}
#[test]
fn test_with_question_mark() -> Result<(), String> {
let result = divide(10.0, 2.0)?;
assert_eq!(result, 5.0);
Ok(())
}
}
fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("除零错误"))
} else {
Ok(a / b)
}
}
何时使用此技能
使用rust-error-handling当您需要:
- 使用Result处理可恢复错误
- 使用Option处理可选值
- 为您的领域创建自定义错误类型
- 使用thiserror定义库错误类型
- 使用anyhow处理应用程序级错误
- 使用?运算符传播错误
- 在不同错误类型之间转换
- 为错误提供上下文
- 实现全面的错误处理
- 编写健壮的调试错误消息
最佳实践
- 使用Result处理可恢复错误,panic处理不可恢复错误
- 在应用程序中使用anyhow::Context提供上下文
- 为库错误类型使用thiserror
- 为自定义错误实现Display和Error trait
- 使用?运算符进行错误传播
- 避免在生产代码中使用unwrap/expect
- 返回错误而不是记录后继续
- 使错误消息具有可操作性和描述性
- 使用类型系统在编译时防止错误
- 在函数文档中记录预期错误
常见陷阱
- 过度使用unwrap()导致生产环境恐慌
- 错误消息中未提供足够上下文
- 不一致地混合panic和Result
- 创建过于泛化的错误类型(如String)
- 未实现From进行错误转换
- 使用let _ = result忽略错误
- 在更适用Option时使用Result
- 在match中未处理所有错误变体
- 创建难以使用的错误类型
- 忘记将错误传播到调用栈上层