以下内容为本人学习过程中的记录笔记,其中可能存在不准确或错误,欢迎勘误及指正

错误处理

Rust中的错误处理不同于Python或者Java中常见的try···except···模式,Rust将错误分为两大类:可恢复错误(recoverable)不可恢复错误(unrecoverable),对于可恢复错误,在编写代码时可以使用Result枚举或者Option枚举来打包,可以更好地实现后续的错误处理逻辑

Result枚举

在Rust中,Result枚举是处理可能产生错误操作的非常好用的工具,其在标准库中的定义可以看做

1
2
3
4
enum Result<T, E> {
Ok(T), // Ok(T):表示操作成功,并且包含一个类型为T的值
Err(E), // Err(E):表示操作失败,并且包含一个类型为E的错误值
}

在我们编写代码的时候,一般就可以这样来使用

1
2
3
4
5
6
fn file_to_read() -> Result<String, io::Error> {  // 注意这里返回值的类型是Result枚举类型
let mut f = File::open("hello.txt")?; // 这里使用了'?'进行错误传递
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}

如果结合之前文章里的Trait对象的话可以实现更加通用的效果

1
2
3
4
5
6
7
fn file_to_read() -> Result<String, Box<dyn std::error::Error>> {  
// 这里返回值使用Trait对象让函数返回实现'Error'Trait的类型
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}

is_ok()、is_err()方法

is_ok()is_err()方法主要是用来判断Result的类型值是否有效或存在错误,用法也很简单:
(1)is_ok()是在Result是Ok时返回true,Err则返回false
(2)is_err()是在Result是Ok时返回false,Ok则返回true

1
2
3
4
5
6
fn main() {
let res: Result<i32, &str> = Ok(5);
if res.is_ok() {
println!("Result is Ok");
}
}

ok()、err()方法

ok()err()方法主要是用来将Result枚举转换为Option枚举,它们的用法如下:
(1)ok()Result<T, E>转换为Option<T>,如果是Err则返回None
(2)err()Result<T, E>转换为Option<E>,如果是Ok则返回None

1
2
3
4
fn main() {
let res: Result<i32, &str> = Ok(5);
let opt = res.ok(); // opt is Some(5)
}
1
2
3
4
fn main() {
let res: Result<i32, &str> = Err("hello");
let opt = res.err(); // opt is Some("hello")
}

Option枚举

和Result枚举类似,Option枚举也是一个相当重要的工具,但它主要是为了解决NULL指针这种容易引起错误的操作,Option枚举在标准库的定义如下

1
2
3
4
enum Option<T> {
None,
Some(T),
}

当使用Option枚举作为返回值时,函数调用者就必须处理返回值为None这种情况,从而实现让程序更稳定的目的

is_some()、is_none()方法

和Result枚举类似,Option枚举的is_some()is_none()也是用来判断值是否有效或为None,即:
(1)is_some()是在Option为Some时返回true,否则返回false
(2)is_none()是在Option为Some时返回false,否则返回true

1
2
3
4
5
6
fn main() {
let opt = Some(5);
if opt.is_some() {
println!("Option has a value");
}
}

直接处理

除了以上介绍方法外,这两个枚举还有一些通用的方法

unwrap()方法

unwrap()方法在Rust中主要用来从Result或Option类型中提取值
(1)如果调用unwrap()时类型是Ok或Some,程序会直接进行提取
(2)如果调用unwrap()时类型是Err或None,程序则会panic并终止运行

1
2
3
4
5
6
7
8
fn main() {
let ok_value: Result<i32, &str> = Ok(10);
let value = ok_value.unwrap();
println!("The value is: {}", value); // 输出: The value is: 10

let err_value: Result<i32, &str> = Err("An error occurred");
let value = err_value.unwrap(); // 这里会 panic
}
1
2
3
4
5
6
7
8
fn main() {
let some_value = Some(10);
let value = some_value.unwrap();
println!("The value is: {}", value); // 输出: The value is: 10

let none_value: Option<i32> = None;
let value = none_value.unwrap(); // 这里会 panic
}

unwrap_or()方法

unwrap_or()方法在Rust中用于处理Option和Result类型,提供一个默认值以防返回值为None或Err

1
2
3
4
5
6
7
8
9
fn main() {
let ok_value: Result<i32, &str> = Ok(10);
let value = ok_value.unwrap_or(0);
println!("The value is: {}", value); // 输出: The value is: 10

let err_value: Result<i32, &str> = Err("An error occurred");
let value = err_value.unwrap_or(0);
println!("The value is: {}", value); // 输出: The value is: 0
}
1
2
3
4
5
6
7
8
9
fn main() {
let some_value = Some(10);
let value = some_value.unwrap_or(0);
println!("The value is: {}", value); // 输出: The value is: 10

let none_value: Option<i32> = None;
let value = none_value.unwrap_or(0);
println!("The value is: {}", value); // 输出: The value is: 0
}

unwrap_or_else()方法

unwrap_or_else()方法类似于unwrap_or(),但其接受一个闭包作为参数,当需要默认值时才会调用该闭包,对于计算开销较大的默认值、需要执行一些逻辑才能获得默认值或者需要对err进行处理时的情况非常有用,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn main() {
let ok_value: Result<i32, &str> = Ok(10);
let value = ok_value.unwrap_or_else(|err| {
println!("Encountered an error: {}", err);
0
});
println!("The value is: {}", value); // 输出: The value is: 10

let err_value: Result<i32, &str> = Err("An error occurred");
let value = err_value.unwrap_or_else(|err| {
println!("Encountered an error: {}", err);
0
});
println!("The value is: {}", value); // 输出: The value is: 0
}
1
2
3
4
5
6
7
8
9
fn main() {
let some_value = Some(10);
let value = some_value.unwrap_or_else(|| 0);
println!("The value is: {}", value); // 输出: The value is: 10

let none_value: Option<i32> = None;
let value = none_value.unwrap_or_else(|| 0);
println!("The value is: {}", value); // 输出: The value is: 0
}

而且,因为unwrap_or_else()是接受闭包作为参数的,这样就可以很方便的捕获环境值,具体怎么使用闭包可以参考之前的文章

unwrap_or_default()方法

unwrap_or_default()方法在Rust中主要用来处理Option和Result类型的值为None或Err时,返回类型的默认值,默认值则由Defaulttrait提供

1
2
3
4
5
6
7
8
9
fn main() {
let some_value = Some(10);
let value = some_value.unwrap_or_default();
println!("The value is: {}", value); // 输出: The value is: 10

let none_value: Option<i32> = None;
let value = none_value.unwrap_or_default();
println!("The value is: {}", value); // 输出: The value is: 0
}
1
2
3
4
5
6
7
8
9
fn main() {
let ok_value: Result<i32, &str> = Ok(10);
let value = ok_value.unwrap_or_default();
println!("The value is: {}", value); // 输出: The value is: 10

let err_value: Result<i32, &str> = Err("An error occurred");
let value = err_value.unwrap_or_default();
println!("The value is: {}", value); // 输出: The value is: 0
}

我们也可以为自定义的类型实现Defaulttrait,使其能调用unwrap_or_default()方法,比如下面这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#[derive(Debug)]
struct MyStruct {
value: i32,
}

impl Default for MyStruct {
fn default() -> Self {
MyStruct { value: 42 }
}
}

fn main() {
let some_value: Option<MyStruct> = Some(MyStruct { value: 10 });
let value = some_value.unwrap_or_default();
println!("The value is: {:?}", value); // 输出: The value is: MyStruct { value: 10 }

let none_value: Option<MyStruct> = None;
let value = none_value.unwrap_or_default();
println!("The value is: {:?}", value); // 输出: The value is: MyStruct { value: 42 }
}

expect()方法

expect()方法和unwrap()方法类似,都是用于从Result或Option类型中提取值,但相比于unwrap(),expect()允许你提供更具描述性的错误信息,以便更容易调试和定位问题

1
2
3
4
5
6
7
8
fn main() {
let ok_value: Result<i32, &str> = Ok(10);
let value = ok_value.expect("Expected Ok, but got Err");
println!("The value is: {}", value); // 输出: The value is: 10

let err_value: Result<i32, &str> = Err("An error occurred");
let value = err_value.expect("Expected Ok, but got Err"); // 这里会 panic,并显示 "Expected Ok, but got Err: An error occurred"
}
1
2
3
4
5
6
7
8
fn main() {
let some_value = Some(10);
let value = some_value.expect("Expected a value, but got None");
println!("The value is: {}", value); // 输出: The value is: 10

let none_value: Option<i32> = None;
let value = none_value.expect("Expected a value, but got None"); // 这里会 panic,并显示 "Expected a value, but got None"
}

map()方法

map()方法用于对Option或Result类型中的值应用一个函数,并返回一个新的Option或Result,通常用于在不改变原始类型的情况下处理值

1
2
3
4
5
6
7
8
9
fn main() {
let some_value = Some(10);
let new_value = some_value.map(|x| x * 2);
println!("The new value is: {:?}", new_value); // 输出: The new value is: Some(20)

let none_value: Option<i32> = None;
let new_value = none_value.map(|x| x * 2);
println!("The new value is: {:?}", new_value); // 输出: The new value is: None
}
1
2
3
4
5
6
7
8
9
fn main() {
let ok_value: Result<i32, &str> = Ok(10);
let new_value = ok_value.map(|x| x * 2);
println!("The new value is: {:?}", new_value); // 输出: The new value is: Ok(20)

let err_value: Result<i32, &str> = Err("An error occurred");
let new_value = err_value.map(|x| x * 2);
println!("The new value is: {:?}", new_value); // 输出: The new value is: Err("An error occurred")
}

对于Result类型,还提供了map_err()方法以适用发生错误的场景

1
2
3
4
5
6
7
8
9
fn main() {
let ok_value: Result<i32, &str> = Ok(10);
let new_value = ok_value.map_err(|e| format!("Error: {}", e));
println!("The new value is: {:?}", new_value); // 输出: The new value is: Ok(10)

let err_value: Result<i32, &str> = Err("An error occurred");
let new_value = err_value.map_err(|e| format!("Error: {}", e));
println!("The new value is: {:?}", new_value); // 输出: The new value is: Err("Error: An error occurred")
}

and_then()方法

and_then()方法在Rust中用于在Option或Result类型中链式处理值。其作用与map()方法类似,但不同之处在于and_then()期望闭包返回一个新的Option或Result,从而使得链式处理更灵活

1
2
3
4
5
6
7
8
9
fn main() {
let some_value = Some(10);
let new_value = some_value.and_then(|x| Some(x * 2));
println!("The new value is: {:?}", new_value); // 输出: The new value is: Some(20)

let none_value: Option<i32> = None;
let new_value = none_value.and_then(|x| Some(x * 2));
println!("The new value is: {:?}", new_value); // 输出: The new value is: None
}
1
2
3
4
5
6
7
8
9
fn main() {
let ok_value: Result<i32, &str> = Ok(10);
let new_value = ok_value.and_then(|x| Ok(x * 2));
println!("The new value is: {:?}", new_value); // 输出: The new value is: Ok(20)

let err_value: Result<i32, &str> = Err("An error occurred");
let new_value = err_value.and_then(|x| Ok(x * 2));
println!("The new value is: {:?}", new_value); // 输出: The new value is: Err("An error occurred")
}

其实在生产环境中,应该避免直接使用unwrap()或expect(),因为其会在发生错误时导致程序崩溃,除非错误是可预见的或期望发生的,否则应该更多使用类似unwrap_or()、unwrap_or_else()这类方法

模式匹配

如上所述,我们在生产环境中应当避免直接使用一些会导致程序奔溃的方法,而当同时需要针对错误的类型实现更加复杂的处理逻辑时,上面的直接处理方法可能就存在一定局限,这时候我们也可以使用模式匹配来细分各种错误的类型进行分别处理,比如下面这样

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

fn read_file_content(file_path: &str) -> Result<String, io::Error> {
let mut file = File::open(file_path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}

fn main() {
// 使用模式匹配对不同情况进行处理
match read_file_content("example.txt") {
Ok(content) => println!("File content: {}", content),
Err(e) => println!("Failed to read file: {}", e),
}
}

错误传递

在本文最开始的示例代码中,我们使用到了?操作符进行函数间错误的传递,在Rust中,?操作符主要用于简化错误处理和传递,即当一个函数返回Result或Option类型时,可以使用?操作符来自动处理错误或无值的情况,并将它们传递给函数调用者来进行后续处理

‘?’操作符的工作原理

(1)对于Result类型:如果表达式返回Ok,则?操作符将其解包并返回其中的值;如果返回Err,则?操作符会将Err提前返回,结束当前函数的执行
(2)对于Option类型:如果表达式返回Some,则?操作符将其解包并返回其中的值;如果返回None,则?操作符会将None提前返回,结束当前函数的执行

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

fn read_file_content(file_path: &str) -> Result<String, io::Error> {
let mut file = File::open(file_path)?; // 使用'?'进行错误处理和传递
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}

fn main() -> Result<(), io::Error> {
let content = read_file_content("example.txt")?;
println!("File content: {}", content);
Ok(())
}

在以上示例中可以看出,?操作符大大简化了错误处理

‘?’操作符的注意事项

返回类型必须是Result或Option

使用?操作符的函数必须返回Result或Option类型,如果尝试在返回类型不是Result或Option的函数中使用,编译器就会报错,比如下面的例子

1
2
3
4
5
6
fn invalid_use_of_question_mark(file_path: &str) -> String {
let mut file = std::fs::File::open(file_path)?; // 此处会发生编译错误,因为函数返回类型为String
let mut content = String::new();
file.read_to_string(&mut content)?;
content
}

错误类型的转换

使用?操作符时,函数的错误类型必须与调用链中的错误类型一致,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std::io;

fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
let num: i32 = s.trim().parse()?;
Ok(num)
}

fn main() -> Result<(), io::Error> {
let s = "195";
// 下面的代码会发生错误,因为parse_number函数传递的是std::num::ParseIntError错误
// 而main函数可能返回的是io::Error错误
let number = parse_number(&s)?;
println!("Parsed number: {}", number);
Ok(())
}

如果不一致,但是又确有这样的需求时,就需要通过Fromtrait或显式的错误转换来解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
use std::fs::File;
use std::io::{self, Read};

#[allow(dead_code)]
#[derive(Debug)]
enum MyError {
Io(io::Error),
Parse(std::num::ParseIntError),
}

impl From<io::Error> for MyError {
fn from(err: io::Error) -> MyError {
MyError::Io(err)
}
}

impl From<std::num::ParseIntError> for MyError {
fn from(err: std::num::ParseIntError) -> MyError {
MyError::Parse(err)
}
}

fn read_file_content(file_path: &str) -> Result<String, MyError> {
let mut file = File::open(file_path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
}

fn parse_number(s: &str) -> Result<i32, MyError> {
let num: i32 = s.trim().parse()?;
Ok(num)
}

fn main() -> Result<(), MyError> {
let content = read_file_content("example.txt")?;
let number = parse_number(&content)?;
println!("Parsed number: {}", number);
Ok(())
}

不要滥用’?’

虽然?操作符非常方便,但也不应滥用,在某些情况下,明确处理错误可能更合适

确保错误传递的合理性

当使用?操作符传递错误时,应确保错误处理的设计合理,某些错误可能需要立即处理,而不是传递