Rust 错误处理详解
概述
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 的错误处理机制:
-
不可恢复错误:
- 使用
panic!宏 - 程序会终止
- 适用于无法恢复的错误
- 使用
-
可恢复错误:
- 使用
Result<T, E>类型 - 可以处理和传播错误
- 使用
?运算符简化错误传播
- 使用
-
错误处理方法:
unwrap()和expect()unwrap_or()和unwrap_or_else()match表达式?运算符
-
自定义错误类型:
- 定义自己的错误枚举
- 使用
thiserror简化错误定义 - 使用
anyhow简化错误处理
-
最佳实践:
- 使用 Result 处理可恢复错误
- 使用 panic! 处理不可恢复错误
- 提供有意义的错误消息
- 使用错误上下文
Rust 的错误处理系统强制你显式处理所有可能的错误,这虽然增加了一些初始的编码工作量,但大大提高了代码的可靠性和安全性。
下一步
在下一教程中,我们将学习 Rust 的模块与包管理,包括:
-
模块系统
-
包和 crate
-
使用外部包
-
工作空间
继续学习 Rust,掌握这门强大语言的更多特性!

