Result 与错误处理
学习目标
- 理解
Result<T, E> 的设计
- 掌握
? 运算符
- 学会
unwrap、expect 的使用场景
- 理解
From trait 实现错误转换
核心概念
Result 定义
enum Result<T, E> {
Ok(T),
Err(E),
}
基本用法
use std::num::ParseIntError;
fn parse_number(s: &str) -> Result<i32, ParseIntError> {
s.parse::<i32>()
}
fn main() {
match parse_number("42") {
Ok(n) => println!("解析成功: {}", n),
Err(e) => println!("解析失败: {}", e),
}
match parse_number("abc") {
Ok(n) => println!("解析成功: {}", n),
Err(e) => println!("解析失败: {}", e),
}
}
? 运算符
use std::fs;
use std::io;
fn read_username() -> Result<String, io::Error> {
let content = fs::read_to_string("username.txt")?;
Ok(content.trim().to_string())
}
fn read_username_verbose() -> Result<String, io::Error> {
let content = match fs::read_to_string("username.txt") {
Ok(c) => c,
Err(e) => return Err(e),
};
Ok(content.trim().to_string())
}
链式 ? 调用
use std::fs::File;
use std::io::{self, Read};
fn read_config() -> Result<String, io::Error> {
let mut file = File::open("config.txt")?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}
自定义错误类型
use std::fmt;
use std::num::ParseIntError;
#[derive(Debug)]
enum AppError {
ParseError(ParseIntError),
NotFound(String),
IoError(std::io::Error),
}
impl fmt::Display for AppError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AppError::ParseError(e) => write!(f, "解析错误: {}", e),
AppError::NotFound(name) => write!(f, "未找到: {}", name),
AppError::IoError(e) => write!(f, "IO 错误: {}", e),
}
}
}
impl From<ParseIntError> for AppError {
fn from(e: ParseIntError) -> Self {
AppError::ParseError(e)
}
}
impl From<std::io::Error> for AppError {
fn from(e: std::io::Error) -> Self {
AppError::IoError(e)
}
}
fn process() -> Result<i32, AppError> {
let content = std::fs::read_to_string("data.txt")?;
let number: i32 = content.trim().parse()?;
Ok(number * 2)
}
Result 的组合子
fn main() {
let ok: Result<i32, String> = Ok(5);
let err: Result<i32, String> = Err(String::from("失败"));
let a = ok.map(|n| n * 2);
let b = err.map_err(|e| e.to_uppercase());
let c = ok.and_then(|n| {
if n > 0 { Ok(n.to_string()) } else { Err(String::from("非正数")) }
});
let d = err.unwrap_or(0);
let e = err.or_else(|_| Ok(42));
}
unwrap 和 expect
fn main() {
let ok: Result<i32, &str> = Ok(42);
let x = ok.unwrap();
let y = ok.expect("应该成功");
}
多种错误类型的处理
use std::fs;
use std::num::ParseIntError;
fn read_number() -> Result<i32, Box<dyn std::error::Error>> {
let content = fs::read_to_string("number.txt")?;
let number = content.trim().parse::<i32>()?;
Ok(number)
}
enum MyError {
Io(std::io::Error),
Parse(ParseIntError),
}
impl From<std::io::Error> for MyError {
fn from(e: std::io::Error) -> Self { MyError::Io(e) }
}
impl From<ParseIntError> for MyError {
fn from(e: ParseIntError) -> Self { MyError::Parse(e) }
}
实践练习
练习 1:安全除法
fn safe_divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("除数不能为零"))
} else {
Ok(a / b)
}
}
fn main() {
let cases = [(10.0, 3.0), (5.0, 0.0), (100.0, 7.0)];
for (a, b) in &cases {
match safe_divide(*a, *b) {
Ok(result) => println!("{} / {} = {:.2}", a, b, result),
Err(e) => println!("错误: {}", e),
}
}
}
练习 2:配置文件解析
use std::collections::HashMap;
#[derive(Debug)]
enum ConfigError {
MissingField(String),
InvalidValue(String),
}
fn parse_config(input: &str) -> Result<HashMap<String, String>, ConfigError> {
let mut config = HashMap::new();
for line in input.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let parts: Vec<&str> = line.splitn(2, '=').collect();
if parts.len() != 2 {
return Err(ConfigError::InvalidValue(line.to_string()));
}
config.insert(parts[0].trim().to_string(), parts[1].trim().to_string());
}
Ok(config)
}
fn main() {
let input = "host=localhost\nport=8080\n# 这是注释\nname=myapp";
match parse_config(input) {
Ok(config) => println!("{:?}", config),
Err(e) => println!("解析失败: {:?}", e),
}
}
练习 3:链式错误处理
use std::fs;
use std::num::ParseIntError;
fn sum_file_numbers(path: &str) -> Result<i64, Box<dyn std::error::Error>> {
let content = fs::read_to_string(path)?;
let mut sum = 0i64;
for line in content.lines() {
let num: i64 = line.trim().parse()?;
sum += num;
}
Ok(sum)
}
fn main() {
match sum_file_numbers("numbers.txt") {
Ok(sum) => println!("总和: {}", sum),
Err(e) => println!("错误: {}", e),
}
}
常见错误
1. 忘记处理错误
fn risky() -> Result<i32, String> { Ok(42) }
fn main() {
risky();
match risky() {
Ok(v) => println!("{}", v),
Err(e) => eprintln!("{}", e),
}
}
2. ? 用在返回 Result 的 main
fn main() -> Result<(), Box<dyn std::error::Error>> {
let content = std::fs::read_to_string("file.txt")?;
println!("{}", content);
Ok(())
}
小结
| 概念 | 说明 |
|---|
Ok(T) | 成功值 |
Err(E) | 错误值 |
? | 成功继续,失败提前返回 |
map | 转换 Ok 值 |
and_then | 链式可能失败的操作 |
From | 实现错误类型自动转换 |
Box<dyn Error> | 通用错误类型 |