Rust没有异常机制,而是通过Result<T, E>和Option<T>在类型系统中表达可能失败的操作。这让错误处理变得显式、安全且可预测。本课将全面掌握Rust的错误处理哲学。
学习目标:深入Result和Option、掌握?运算符、学会自定义错误类型、理解panic vs Result的选择
可恢复错误 → Result<T, E>:文件不存在、网络超时、解析失败……调用者应该决定如何处理
不可恢复错误 → panic!:数组越界、逻辑不可能的情况……程序应该立即停止
可能缺失的值 → Option<T>:查找失败、可选配置……这不是"错误",只是"没有值"
fn main() {
// panic会立即终止当前线程
// panic!("出错了!"); // 💥 程序崩溃
// 常见的自动panic场景
// let v = vec![1, 2, 3];
// v[99]; // ❌ 数组越界 → panic
// Option的unwrap在None时panic
// let x: Option = None;
// x.unwrap(); // ❌ panic: called `Option::unwrap()` on a `None` value
// 安全替代
let x: Option = None;
println!("默认值: {}", x.unwrap_or(0));
// panic会展开调用栈(默认),可以设置直接终止
// Cargo.toml: [profile.release] panic = "abort"
}
✅ 验证通过
enum Result<T, E> {
Ok(T), // 成功,包含值T
Err(E), // 失败,包含错误E
}
use std::fs;
use std::io;
use std::num::ParseIntError;
fn main() {
// 打开文件返回Result
let result = fs::read_to_string("nonexistent.txt");
match result {
Ok(content) => println!("内容: {}", content),
Err(e) => println!("读取失败: {}", e), // 实际会走这里
}
// 更简洁的处理方式
// unwrap —— Ok返回值,Err则panic
// let content = fs::read_to_string("nonexistent.txt").unwrap(); // ❌ panic!
// expect —— 同unwrap但可自定义消息
// let content = fs::read_to_string("nonexistent.txt").expect("必须存在的文件"); // ❌ panic!
// unwrap_or_default —— 错误时返回默认值
let content = fs::read_to_string("nonexistent.txt").unwrap_or_default();
println!("默认内容: '{}'", content); // 空字符串
// unwrap_or —— 错误时返回指定值
let content = fs::read_to_string("nonexistent.txt")
.unwrap_or(String::from("(文件不存在)"));
println!("替代内容: {}", content);
}
✅ 验证通过
?是Rust错误处理的语法糖,让错误传播变得简洁:
use std::fs::File;
use std::io::{self, Read};
// 不用?的写法(啰嗦)
fn read_username_from_file_verbose() -> Result<String, io::Error> {
let file = match File::open("username.txt") {
Ok(f) => f,
Err(e) => return Err(e), // 手动传播错误
};
let mut username = String::new();
match file.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(e) => Err(e), // 手动传播错误
}
}
// 用?的写法(简洁)
fn read_username_from_file() -> Result<String, io::Error> {
let mut file = File::open("username.txt")?; // Err自动返回
let mut username = String::new();
file.read_to_string(&mut username)?; // Err自动返回
Ok(username)
}
// 更简洁的链式调用
fn read_username_chain() -> Result<String, io::Error> {
let mut username = String::new();
File::open("username.txt")?.read_to_string(&mut username)?;
Ok(username)
}
// ?也可以用在Option上
fn last_char_of_first_line(text: &str) -> Option<char> {
let line = text.lines().next()?; // None自动返回
line.chars().last() // None自动返回
}
fn main() {
// ?在main中的使用(main返回Result)
let result = read_username_from_file();
match result {
Ok(name) => println!("用户名: {}", name),
Err(e) => println!("读取失败: {}", e),
}
// Option上的?
let text = "Hello\nWorld";
println!("第一行最后一个字符: {:?}", last_char_of_first_line(text));
println!("空字符串: {:?}", last_char_of_first_line(""));
}
✅ 验证通过
Result上下文中:Err(e)会自动转换为返回类型的ErrOption上下文中:None会自动返回Noneuse std::fmt;
use std::error::Error;
use std::num::ParseIntError;
// 自定义错误类型
#[derive(Debug)]
enum AppError {
Io(std::io::Error),
Parse(ParseIntError),
NotFound(String),
InvalidInput { field: String, reason: String },
}
// 实现Display
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(e) => write!(f, "解析错误: {}", e),
AppError::NotFound(name) => write!(f, "未找到: {}", name),
AppError::InvalidInput { field, reason } => {
write!(f, "无效输入 '{}': {}", field, reason)
}
}
}
}
// 实现Error特征
impl Error for AppError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
AppError::Io(e) => Some(e),
AppError::Parse(e) => Some(e),
_ => None,
}
}
}
// 从其他错误类型转换
impl From for AppError {
fn from(e: std::io::Error) -> Self { AppError::Io(e) }
}
impl From for AppError {
fn from(e: ParseIntError) -> Self { AppError::Parse(e) }
}
// 使用自定义错误
fn parse_config(value: &str) -> Result<i32, AppError> {
if value.is_empty() {
return Err(AppError::InvalidInput {
field: "value".to_string(),
reason: "不能为空".to_string(),
});
}
let num: i32 = value.parse()?; // ParseIntError自动转为AppError
Ok(num)
}
fn main() {
// 正常解析
match parse_config("42") {
Ok(n) => println!("配置值: {}", n),
Err(e) => println!("错误: {}", e),
}
// 解析失败
match parse_config("abc") {
Ok(n) => println!("配置值: {}", n),
Err(e) => println!("错误: {}", e),
}
// 空输入
match parse_config("") {
Ok(n) => println!("配置值: {}", n),
Err(e) => println!("错误: {}", e),
}
// 遍历错误链
if let Err(e) = parse_config("xyz") {
println!("\n错误详情:");
println!(" 描述: {}", e);
if let Some(source) = e.source() {
println!(" 原因: {}", source);
}
}
}
✅ 验证通过
社区最常用的两个错误处理库,让错误处理更简洁:
// thiserror —— 用于库代码,自动实现Error特征
// Cargo.toml: thiserror = "2"
//
// #[derive(thiserror::Error, Debug)]
// enum AppError {
// #[error("IO错误: {0}")]
// Io(#[from] std::io::Error),
// #[error("未找到: {0}")]
// NotFound(String),
// }
// anyhow —— 用于应用代码,统一错误类型
// Cargo.toml: anyhow = "1"
//
// use anyhow::{Context, Result};
// fn read_config() -> Result {
// let content = fs::read_to_string("config.toml")
// .context("无法读取配置文件")?;
// Ok(parse(content))
// }
fn main() {
println!("thiserror: 用于库,提供类型化错误");
println!("anyhow: 用于应用,简化错误处理");
println!("选择原则: 库→thiserror, 应用→anyhow");
}
use std::collections::HashMap;
use std::fmt;
#[derive(Debug)]
enum ConfigError {
MissingKey(String),
InvalidType { key: String, expected: String, got: String },
ParseError { key: String, value: String, reason: String },
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ConfigError::MissingKey(k) => write!(f, "缺少必需配置项: {}", k),
ConfigError::InvalidType { key, expected, got } => {
write!(f, "配置项'{}'类型错误: 期望{},实际{}", key, expected, got)
}
ConfigError::ParseError { key, value, reason } => {
write!(f, "配置项'{}'值'{}'解析失败: {}", key, value, reason)
}
}
}
}
struct Config {
values: HashMap<String, String>,
}
impl Config {
fn new() -> Self {
Config { values: HashMap::new() }
}
fn from_str(s: &str) -> Result<Self, ConfigError> {
let mut config = Config::new();
for line in s.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((key, value)) = line.split_once('=') {
config.values.insert(key.trim().to_string(), value.trim().to_string());
}
}
Ok(config)
}
fn get(&self, key: &str) -> Result<&str, ConfigError> {
self.values.get(key)
.map(|s| s.as_str())
.ok_or_else(|| ConfigError::MissingKey(key.to_string()))
}
fn get_int(&self, key: &str) -> Result<i32, ConfigError> {
let value = self.get(key)?;
value.parse().map_err(|_| ConfigError::ParseError {
key: key.to_string(),
value: value.to_string(),
reason: "不是有效的整数".to_string(),
})
}
fn get_bool(&self, key: &str) -> Result<bool, ConfigError> {
let value = self.get(key)?;
match value {
"true" | "yes" | "1" => Ok(true),
"false" | "no" | "0" => Ok(false),
_ => Err(ConfigError::InvalidType {
key: key.to_string(),
expected: "布尔值".to_string(),
got: value.to_string(),
}),
}
}
fn get_float(&self, key: &str) -> Result<f64, ConfigError> {
let value = self.get(key)?;
value.parse().map_err(|_| ConfigError::ParseError {
key: key.to_string(),
value: value.to_string(),
reason: "不是有效的浮点数".to_string(),
})
}
}
fn main() {
let config_text = r#"
# 服务器配置
host = 127.0.0.1
port = 8080
debug = true
timeout = 30.5
"#;
let config = Config::from_str(config_text).expect("配置解析失败");
// 读取各种类型
println!("host: {}", config.get("host").unwrap_or("localhost"));
println!("port: {}", config.get_int("port").unwrap_or(3000));
println!("debug: {}", config.get_bool("debug").unwrap_or(false));
println!("timeout: {:.1}s", config.get_float("timeout").unwrap_or(10.0));
// 错误情况
println!("\n--- 错误处理演示 ---");
match config.get("missing_key") {
Ok(v) => println!("值: {}", v),
Err(e) => println!("❌ {}", e),
}
match config.get_int("host") {
Ok(v) => println!("端口号: {}", v),
Err(e) => println!("❌ {}", e),
}
match config.get_bool("port") {
Ok(v) => println!("布尔: {}", v),
Err(e) => println!("❌ {}", e),
}
}
✅ 验证通过
编写safe_divide(a: f64, b: f64) -> Result<f64, String>,处理除零和溢出。
编写函数解析用户输入的年龄(字符串→u8),处理各种错误情况(非数字、超出范围等)。
模拟:读取文件→解析JSON→验证数据。用?运算符链式处理,自定义错误类型支持From转换。
🔒 下一课解锁:泛型 —— 编写通用的、可复用的代码