错误处理
Rust 的错误处理哲学是让错误处理变得明确和可控。Rust 将错误分为两大类:可恢复的错误(recoverable errors)和不可恢复的错误(unrecoverable errors)。
不可恢复错误与 panic!
基本 panic!
rust
fn main() {
panic!("程序崩溃了!");
// 这行代码不会执行
println!("这行不会被打印");
}
数组越界引发的 panic
rust
fn main() {
let v = vec![1, 2, 3];
// 这会引发 panic
v[99];
}
设置 panic 行为
rust
// 在 Cargo.toml 中设置
// [profile.release]
// panic = 'abort'
fn main() {
// 在 debug 模式下,panic 会展开栈
// 在 release 模式下,可以设置为直接终止
panic!("Something went wrong!");
}
使用 backtrace
bash
# 设置环境变量查看详细错误信息
RUST_BACKTRACE=1 cargo run
RUST_BACKTRACE=full cargo run
Result 枚举
Result 的定义
rust
enum Result<T, E> {
Ok(T),
Err(E),
}
基本 Result 使用
rust
use std::fs::File;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => panic!("打开文件时出错:{:?}", error),
};
}
匹配不同的错误
rust
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);
}
},
};
}
使用闭包简化错误处理
rust
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);
}
});
}
Result 的方法
unwrap 和 expect
rust
use std::fs::File;
fn main() {
// unwrap:如果是 Ok 返回值,如果是 Err 则 panic
let greeting_file = File::open("hello.txt").unwrap();
// expect:类似 unwrap,但可以自定义 panic 消息
let greeting_file = File::open("hello.txt")
.expect("hello.txt 应该包含在这个项目中");
}
其他有用的方法
rust
fn main() {
let result: Result<i32, &str> = Ok(42);
// is_ok() 和 is_err()
println!("是否成功:{}", result.is_ok());
println!("是否失败:{}", result.is_err());
// unwrap_or() - 提供默认值
let value = result.unwrap_or(0);
println!("值:{}", value);
// unwrap_or_else() - 使用闭包计算默认值
let value = result.unwrap_or_else(|_| 0);
println!("值:{}", value);
// map() - 转换 Ok 中的值
let doubled = result.map(|x| x * 2);
println!("翻倍:{:?}", doubled);
// map_err() - 转换 Err 中的值
let mapped_err = result.map_err(|e| format!("错误:{}", e));
println!("映射错误:{:?}", mapped_err);
}
错误传播
手动传播错误
rust
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let username_file_result = File::open("username.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 main() {
match read_username_from_file() {
Ok(username) => println!("用户名:{}", username),
Err(error) => println!("读取用户名失败:{}", error),
}
}
? 运算符
rust
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut username_file = File::open("username.txt")?;
let mut username = String::new();
username_file.read_to_string(&mut username)?;
Ok(username)
}
// 更简洁的版本
fn read_username_from_file_short() -> Result<String, io::Error> {
let mut username = String::new();
File::open("username.txt")?.read_to_string(&mut username)?;
Ok(username)
}
// 最简洁的版本
fn read_username_from_file_shortest() -> Result<String, io::Error> {
std::fs::read_to_string("username.txt")
}
? 运算符与 Option
rust
fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}
fn main() {
let text = "Hello\nWorld";
match last_char_of_first_line(text) {
Some(ch) => println!("最后一个字符:{}", ch),
None => println!("没有找到字符"),
}
}
main 函数中的 ?
rust
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}
自定义错误类型
简单的自定义错误
rust
#[derive(Debug)]
struct CustomError {
message: String,
}
impl CustomError {
fn new(message: &str) -> CustomError {
CustomError {
message: message.to_string(),
}
}
}
impl std::fmt::Display for CustomError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "自定义错误:{}", self.message)
}
}
impl std::error::Error for CustomError {}
fn might_fail(should_fail: bool) -> Result<String, CustomError> {
if should_fail {
Err(CustomError::new("操作失败"))
} else {
Ok("操作成功".to_string())
}
}
fn main() {
match might_fail(true) {
Ok(message) => println!("{}", message),
Err(error) => println!("错误:{}", error),
}
}
使用枚举定义错误类型
rust
#[derive(Debug)]
enum MathError {
DivisionByZero,
NegativeSquareRoot,
InvalidInput(String),
}
impl std::fmt::Display for MathError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
MathError::DivisionByZero => write!(f, "除零错误"),
MathError::NegativeSquareRoot => write!(f, "负数开方错误"),
MathError::InvalidInput(msg) => write!(f, "无效输入:{}", msg),
}
}
}
impl std::error::Error for MathError {}
fn divide(a: f64, b: f64) -> Result<f64, MathError> {
if b == 0.0 {
Err(MathError::DivisionByZero)
} else {
Ok(a / b)
}
}
fn sqrt(x: f64) -> Result<f64, MathError> {
if x < 0.0 {
Err(MathError::NegativeSquareRoot)
} else {
Ok(x.sqrt())
}
}
fn main() {
// 测试除法
match divide(10.0, 2.0) {
Ok(result) => println!("10 / 2 = {}", result),
Err(error) => println!("错误:{}", error),
}
match divide(10.0, 0.0) {
Ok(result) => println!("10 / 0 = {}", result),
Err(error) => println!("错误:{}", error),
}
// 测试开方
match sqrt(-4.0) {
Ok(result) => println!("sqrt(-4) = {}", result),
Err(error) => println!("错误:{}", error),
}
}
错误转换
From trait
rust
use std::fs::File;
use std::io;
use std::num::ParseIntError;
#[derive(Debug)]
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)
}
}
impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
AppError::Io(err) => write!(f, "IO 错误:{}", err),
AppError::Parse(err) => write!(f, "解析错误:{}", err),
}
}
}
impl std::error::Error for AppError {}
fn read_and_parse_file() -> Result<i32, AppError> {
let content = std::fs::read_to_string("number.txt")?; // io::Error 自动转换
let number: i32 = content.trim().parse()?; // ParseIntError 自动转换
Ok(number)
}
fn main() {
match read_and_parse_file() {
Ok(number) => println!("读取到的数字:{}", number),
Err(error) => println!("错误:{}", error),
}
}
错误处理最佳实践
何时使用 panic!
rust
fn main() {
// 1. 示例、原型代码和测试
let v = vec![1, 2, 3];
let element = v[0]; // 在示例中可以使用
// 2. 当你比编译器知道更多信息时
let home: std::net::IpAddr = "127.0.0.1"
.parse()
.expect("硬编码的 IP 地址应该是有效的");
// 3. 不可恢复的错误
if std::env::args().len() < 2 {
panic!("程序需要至少一个参数");
}
}
何时使用 Result
rust
use std::fs::File;
use std::io::{self, Read};
// 1. 可能失败的操作
fn read_config_file() -> Result<String, io::Error> {
std::fs::read_to_string("config.toml")
}
// 2. 用户输入验证
fn validate_age(age: i32) -> Result<i32, String> {
if age < 0 {
Err("年龄不能为负数".to_string())
} else if age > 150 {
Err("年龄不能超过 150".to_string())
} else {
Ok(age)
}
}
// 3. 网络操作
fn fetch_data(url: &str) -> Result<String, Box<dyn std::error::Error>> {
// 模拟网络请求
if url.is_empty() {
Err("URL 不能为空".into())
} else {
Ok("数据".to_string())
}
}
fn main() {
// 处理配置文件读取
match read_config_file() {
Ok(config) => println!("配置:{}", config),
Err(_) => println!("使用默认配置"),
}
// 处理用户输入
match validate_age(25) {
Ok(age) => println!("有效年龄:{}", age),
Err(error) => println!("无效年龄:{}", error),
}
}
错误处理模式
rust
use std::fs::File;
use std::io::{self, Read};
// 1. 早期返回模式
fn process_file(filename: &str) -> Result<String, io::Error> {
let mut file = File::open(filename)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
// 处理内容
Ok(contents.to_uppercase())
}
// 2. 链式调用模式
fn process_file_chain(filename: &str) -> Result<String, io::Error> {
std::fs::read_to_string(filename)
.map(|contents| contents.to_uppercase())
}
// 3. 组合多个 Result
fn combine_results() -> Result<i32, Box<dyn std::error::Error>> {
let a: Result<i32, _> = "42".parse();
let b: Result<i32, _> = "24".parse();
let sum = a? + b?;
Ok(sum)
}
fn main() {
// 测试不同的错误处理模式
match process_file("test.txt") {
Ok(content) => println!("处理结果:{}", content),
Err(error) => println!("处理失败:{}", error),
}
}
实际应用示例
配置文件解析器
rust
use std::collections::HashMap;
use std::fs;
#[derive(Debug)]
enum ConfigError {
FileNotFound,
ParseError(String),
MissingKey(String),
}
impl std::fmt::Display for ConfigError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ConfigError::FileNotFound => write!(f, "配置文件未找到"),
ConfigError::ParseError(msg) => write!(f, "解析错误:{}", msg),
ConfigError::MissingKey(key) => write!(f, "缺少配置项:{}", key),
}
}
}
impl std::error::Error for ConfigError {}
struct Config {
settings: HashMap<String, String>,
}
impl Config {
fn load(filename: &str) -> Result<Config, ConfigError> {
let content = fs::read_to_string(filename)
.map_err(|_| ConfigError::FileNotFound)?;
let mut settings = HashMap::new();
for line in content.lines() {
if line.trim().is_empty() || line.starts_with('#') {
continue;
}
let parts: Vec<&str> = line.splitn(2, '=').collect();
if parts.len() != 2 {
return Err(ConfigError::ParseError(
format!("无效的配置行:{}", line)
));
}
settings.insert(
parts[0].trim().to_string(),
parts[1].trim().to_string(),
);
}
Ok(Config { settings })
}
fn get(&self, key: &str) -> Result<&String, ConfigError> {
self.settings.get(key)
.ok_or_else(|| ConfigError::MissingKey(key.to_string()))
}
fn get_or_default(&self, key: &str, default: &str) -> String {
self.settings.get(key)
.cloned()
.unwrap_or_else(|| default.to_string())
}
}
fn main() {
match Config::load("app.conf") {
Ok(config) => {
println!("配置加载成功");
match config.get("database_url") {
Ok(url) => println!("数据库 URL:{}", url),
Err(error) => println!("错误:{}", error),
}
let port = config.get_or_default("port", "8080");
println!("端口:{}", port);
}
Err(error) => {
println!("配置加载失败:{}", error);
}
}
}
练习
练习 1:计算器错误处理
创建一个计算器,处理除零、无效输入等错误。
练习 2:文件处理器
编写一个文件处理程序,优雅地处理文件不存在、权限不足等错误。
练习 3:网络客户端
模拟一个网络客户端,处理连接超时、服务器错误等情况。
练习 4:数据验证器
创建一个数据验证系统,处理各种验证错误。
下一步
掌握了错误处理后,您可以继续学习:
- 泛型 - 编写通用代码
- 特征 (Traits) - 定义共同行为
- 生命周期 - 引用的有效性
良好的错误处理是编写健壮 Rust 程序的关键!