Skip to content

错误处理

Rust 将错误分为两类:可恢复错误(如文件未找到)和不可恢复错误(如数组越界)。对于前者使用 Result<T, E>,后者使用 panic!

当程序遇到无法处理的错误时,可以使用 panic! 宏终止程序。

fn main() {
panic!("程序崩溃了!");
}

输出:

thread 'main' panicked at '程序崩溃了!', src/main.rs:2:5

某些操作会导致 panic:

fn main() {
let v = vec![1, 2, 3];
v[99]; // 越界访问,panic!
}

设置环境变量查看完整调用栈:

Terminal window
RUST_BACKTRACE=1 cargo run

Cargo.toml 中配置:

[profile.release]
panic = 'abort' # 直接终止,不展开栈(二进制更小)

默认是 unwind(展开栈,清理数据)。

大多数错误应该是可恢复的,使用 Result 类型处理:

enum Result<T, E> {
Ok(T), // 成功,包含结果值
Err(E), // 失败,包含错误信息
}
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("无法打开文件: {:?}", error);
}
};
}
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
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);
}
},
};
}

使用闭包更简洁:

use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = 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);
}
});
}

成功时返回 Ok 中的值,失败时 panic!

use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
}

unwrap 类似,但可以自定义错误信息:

use std::fs::File;
fn main() {
let f = File::open("hello.txt")
.expect("无法打开 hello.txt");
}

建议:在生产代码中优先使用 expect,因为它提供了更有意义的错误信息。

失败时返回默认值:

fn main() {
let port = std::env::var("PORT")
.unwrap_or(String::from("8080"));
println!("端口: {}", port);
}

失败时执行闭包:

fn main() {
let port = std::env::var("PORT")
.unwrap_or_else(|_| {
println!("未设置 PORT,使用默认值");
String::from("8080")
});
}

? 操作符可以简化错误传播:

use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?; // 如果失败,提前返回 Err
let mut s = String::new();
f.read_to_string(&mut s)?; // 如果失败,提前返回 Err
Ok(s)
}

? 的作用:

  • 如果 ResultOk,返回 Ok 中的值
  • 如果 ResultErr,从函数返回 Err
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut s = String::new();
File::open("hello.txt")?.read_to_string(&mut s)?;
Ok(s)
}

标准库提供了更简洁的函数:

use std::fs;
use std::io;
fn read_username_from_file() -> Result<String, io::Error> {
fs::read_to_string("hello.txt")
}

? 也可以用于 Option

fn first_char(text: &str) -> Option<char> {
text.lines().next()?.chars().next()
}
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let f = File::open("hello.txt")?;
Ok(())
}
#[derive(Debug)]
enum AppError {
IoError(std::io::Error),
ParseError(std::num::ParseIntError),
CustomError(String),
}
fn do_something() -> Result<i32, AppError> {
let content = std::fs::read_to_string("number.txt")
.map_err(AppError::IoError)?;
let number: i32 = content.trim().parse()
.map_err(AppError::ParseError)?;
if number < 0 {
return Err(AppError::CustomError("数字不能为负".to_string()));
}
Ok(number)
}
use std::error::Error;
use std::fmt;
#[derive(Debug)]
struct MyError {
message: String,
}
impl fmt::Display for MyError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "错误: {}", self.message)
}
}
impl Error for MyError {}
fn main() -> Result<(), MyError> {
Err(MyError {
message: String::from("出错了"),
})
}
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)
}
}
fn read_and_parse() -> Result<i32, AppError> {
let content = std::fs::read_to_string("number.txt")?; // 自动转换
let number: i32 = content.trim().parse()?; // 自动转换
Ok(number)
}
  1. 示例和原型代码:快速验证想法
  2. 测试代码:断言失败时
  3. 确定不会失败:逻辑上不可能失败时
fn main() {
// 我们知道这是有效的 IP 地址
let home: std::net::IpAddr = "127.0.0.1".parse().unwrap();
}
  1. 违反契约:调用者传入了无效参数
  1. 预期可能失败的操作:文件操作、网络请求等
  2. 库代码:让调用者决定如何处理错误
  3. 可恢复的错误:可以采取替代措施
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Result<Guess, String> {
if value < 1 || value > 100 {
return Err(format!("猜测值必须在 1 到 100 之间,收到: {}", value));
}
Ok(Guess { value })
}
pub fn value(&self) -> i32 {
self.value
}
}

编写一个函数,读取文件内容,使用 ? 传播错误:

use std::fs;
use std::io;
fn read_file(path: &str) -> Result<String, io::Error> {
// 你的代码
}
fn main() {
match read_file("test.txt") {
Ok(content) => println!("内容: {}", content),
Err(e) => println!("错误: {}", e),
}
}

编写一个函数,读取文件 -> 解析为数字 -> 计算平方:

use std::fs;
use std::io;
use std::num::ParseIntError;
#[derive(Debug)]
enum MyError {
Io(io::Error),
Parse(ParseIntError),
}
fn read_and_square(path: &str) -> Result<i32, MyError> {
// 你的代码
}
fn main() {
match read_and_square("number.txt") {
Ok(result) => println!("结果: {}", result),
Err(e) => println!("错误: {:?}", e),
}
}

为练习 2 的 MyError 实现 From trait,使 ? 能自动转换错误类型:

impl From<io::Error> for MyError {
// 你的代码
}
impl From<ParseIntError> for MyError {
// 你的代码
}

创建一个 Email 类型,验证邮箱格式(简单验证:包含 @):

struct Email {
value: String,
}
impl Email {
fn new(email: &str) -> Result<Email, String> {
// 你的代码
}
fn value(&self) -> &str {
&self.value
}
}
fn main() {
match Email::new("user@example.com") {
Ok(email) => println!("有效邮箱: {}", email.value()),
Err(e) => println!("无效: {}", e),
}
match Email::new("invalid-email") {
Ok(email) => println!("有效邮箱: {}", email.value()),
Err(e) => println!("无效: {}", e),
}
}

学完错误处理后,下一章我们将学习 Rust 的模块系统,了解如何组织代码。