Result 与错误处理
学习目标
- 理解
Result<T, E>的设计 - 掌握
?运算符 - 学会
unwrap、expect的使用场景 - 理解
Fromtrait 实现错误转换
核心概念
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")?; // 失败时提前返回 Err
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),
}
}
}
// 实现 From,让 ? 自动转换错误类型
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")?; // io::Error → AppError
let number: i32 = content.trim().parse()?; // ParseIntError → AppError
Ok(number * 2)
}
Result 的组合子
fn main() {
let ok: Result<i32, String> = Ok(5);
let err: Result<i32, String> = Err(String::from("失败"));
// map: 转换 Ok 值
let a = ok.map(|n| n * 2); // Ok(10)
// map_err: 转换 Err 值
let b = err.map_err(|e| e.to_uppercase());
// and_then: 链式操作
let c = ok.and_then(|n| {
if n > 0 { Ok(n.to_string()) } else { Err(String::from("非正数")) }
});
// unwrap_or: 提供默认值
let d = err.unwrap_or(0); // 0
// or_else: 提供备选
let e = err.or_else(|_| Ok(42)); // Ok(42)
}
unwrap 和 expect
fn main() {
let ok: Result<i32, &str> = Ok(42);
// unwrap: Ok 则取出值,Err 则 panic
let x = ok.unwrap(); // 42
// expect: 同上,但 panic 时有自定义消息
let y = ok.expect("应该成功");
// 使用场景:
// 1. 测试代码中
// 2. 你能 100% 确定不会失败时
// 3. 原型开发阶段
}
多种错误类型的处理
use std::fs;
use std::num::ParseIntError;
// 方式一:用 Box<dyn Error>(简单场景)
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
// ❌ 默认 main 返回 ()
// fn main() {
// let content = std::fs::read_to_string("file.txt")?;
// }
// ✅ main 返回 Result
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> | 通用错误类型 |
练习编辑器
rust
Loading...