Rust —— 错误处理

Rust 将错误分为两大类:可恢复错误不可恢复错误。可恢复错误用 Result<T, E> 处理,不可恢复错误用 panic! 宏处理。Rust 没有异常机制,强制你在编译时就考虑错误处理。

1. panic! —— 不可恢复错误

1.1 触发 panic

1
2
3
fn main() {
panic!("crash and burn");
}

程序会打印错误信息、清理栈并退出。

1.2 panic 时的栈展开

默认情况下,panic 会展开栈并清理数据。可以在 Cargo.toml 中配置直接终止:

1
2
[profile.release]
panic = 'abort'

1.3 panic 回溯

设置环境变量查看详细回溯信息:

1
RUST_BACKTRACE=1 cargo run

2. Result —— 可恢复错误

2.1 Result 枚举

1
2
3
4
enum Result<T, E> {
Ok(T),
Err(E),
}

2.2 使用 match 处理

1
2
3
4
5
6
7
8
9
10
11
12
use std::fs::File;

fn main() {
let f = File::open("hello.txt");

let f = match f {
Ok(file) => file,
Err(error) => {
panic!("打开文件出错: {:?}", error);
}
};
}

2.3 匹配不同错误类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}
},
};
}

2.4 使用闭包简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
});
}

3. unwrap 和 expect

3.1 unwrap

unwrapmatch 的快捷方式,成功返回值,失败则 panic

1
2
3
4
5
use std::fs::File;

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

3.2 expect

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

1
2
3
4
5
6
use std::fs::File;

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

推荐: 生产代码中优先使用 expect,提供清晰的上下文信息。

4. 传播错误

4.1 返回 Result

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.txt");

let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};

let mut s = String::new();

match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}

4.2 使用 ? 运算符

? 运算符是传播错误的简写:

1
2
3
4
5
6
7
8
9
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")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}

进一步简化:

1
2
3
4
5
6
7
8
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)
}

最简洁版本:

1
2
3
4
5
6
use std::fs;
use std::io;

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

4.3 ? 运算符用于 Option

1
2
3
fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}

注意: ? 只能用在返回 ResultOption 的函数中。

4.4 main 函数返回 Result

1
2
3
4
5
6
7
use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
let f = File::open("hello.txt")?;
Ok(())
}

5. 何时使用 panic!

5.1 适合 panic 的场景

  • 示例、原型代码:使用 unwrap 快速编写
  • 测试代码:测试失败应该 panic
  • 逻辑保证不会失败:编译器无法理解但你确定的情况
1
2
3
4
5
use std::net::IpAddr;

let home: IpAddr = "127.0.0.1"
.parse()
.expect("硬编码的 IP 地址应该有效");

5.2 应该返回 Result 的场景

  • 预期可能失败:如解析用户输入、网络请求
  • 调用者需要决定如何处理:错误处理策略由调用方决定

5.3 违反约定时 panic

当代码接收到无效值时,应该 panic:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pub struct Guess {
value: i32,
}

impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess 值必须在 1 到 100 之间,得到 {}", value);
}

Guess { value }
}

pub fn value(&self) -> i32 {
self.value
}
}

6. 常用错误处理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// unwrap_or:提供默认值
let num = "abc".parse::<i32>().unwrap_or(0);

// unwrap_or_else:使用闭包计算默认值
let num = "abc".parse::<i32>().unwrap_or_else(|_| 0);

// unwrap_or_default:使用类型的默认值
let num = "abc".parse::<i32>().unwrap_or_default();

// and_then:链式操作
let result = File::open("file.txt")
.and_then(|mut file| {
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
});

总结

  • panic!:用于不可恢复的错误,程序无法继续执行
  • Result<T, E>:用于可恢复的错误,允许调用者决定如何处理
  • ? 运算符:简化错误传播,使代码更简洁
  • unwrap/expect:快速原型开发,生产代码优先使用 expect

Rust 的错误处理系统强制你在编译时考虑错误情况,虽然初期可能觉得繁琐,但能显著提高代码的健壮性和可靠性。

Hooray!错误处理学习完成!!!