错误处理

panic

  • 如果符合直接panic字符串并且退出。

Option和unwrap

  • option是一种可能遇到不存在的情况,它可能会遇到Some(T)None两种情况。
  • option可以和match进行组合,或者是使用unwrap进行隐式处理(返回Some(T)或者panic)

使用?解开Option

  • 首先使用Option嵌套struct,然后使用?进行嵌套。

组合算子:map

  • 遇到只有一种输入有效的情况之下,使用组合算子 来以模块化的风格管理控制流。
  • Option有一个内置方法map,用于Some-SomeNone->None。多个map()调用串联,更加灵活。
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#![allow(dead_code)]

#[derive(Debug)] enum Food { Apple, Carrot, Potato }

#[derive(Debug)] struct Peeled(Food);
#[derive(Debug)] struct Chopped(Food);
#[derive(Debug)] struct Cooked(Food);

// 削皮。如果没有食物,就返回 `None`。否则返回削好皮的食物。
fn peel(food: Option<Food>) -> Option<Peeled> {
match food {
Some(food) => Some(Peeled(food)),
None => None,
}
}

// 切食物。如果没有食物,就返回 `None`。否则返回切好的食物。
fn chop(peeled: Option<Peeled>) -> Option<Chopped> {
match peeled {
Some(Peeled(food)) => Some(Chopped(food)),
None => None,
}
}

// 烹饪食物。这里,我们使用 `map()` 来替代 `match` 以处理各种情况。
fn cook(chopped: Option<Chopped>) -> Option<Cooked> {
chopped.map(|Chopped(food)| Cooked(food))
}

// 这个函数会完成削皮切块烹饪一条龙。我们把 `map()` 串起来,以简化代码。
fn process(food: Option<Food>) -> Option<Cooked> {
food.map(|f| Peeled(f))
.map(|Peeled(f)| Chopped(f))
.map(|Chopped(f)| Cooked(f))
}

// 在尝试吃食物之前确认食物是否存在是非常重要的!
fn eat(food: Option<Cooked>) {
match food {
Some(food) => println!("Mmm. I love {:?}", food),
None => println!("Oh no! It wasn't edible."),
}
}

fn main() {
let apple = Some(Food::Apple);
let carrot = Some(Food::Carrot);
let potato = None;

let cooked_apple = cook(chop(peel(apple)));
let cooked_carrot = cook(chop(peel(carrot)));

// 现在让我们试试看起来更简单的 `process()`。
let cooked_potato = process(potato);

eat(cooked_apple);
eat(cooked_carrot);
eat(cooked_potato);
}

组合算子:and_then

  • 由于使用map可能会出现多层Option导致混乱,所以有替代方法and_then()。
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
41
42
43
44
45
46
47
48
49
50
51
52
53
#![allow(dead_code)]

#[derive(Debug)] enum Food { CordonBleu, Steak, Sushi }
#[derive(Debug)] enum Day { Monday, Tuesday, Wednesday }

// 我们没有制作寿司所需的原材料(ingredient)(有其他的原材料)。
fn have_ingredients(food: Food) -> Option<Food> {
match food {
Food::Sushi => None,
_ => Some(food),
}
}

// 我们拥有全部食物的食谱,除了法国蓝带猪排(Cordon Bleu)的。
fn have_recipe(food: Food) -> Option<Food> {
match food {
Food::CordonBleu => None,
_ => Some(food),
}
}


// 要做一份好菜,我们需要原材料和食谱。
// 我们可以借助一系列 `match` 来表达这个逻辑:
fn cookable_v1(food: Food) -> Option<Food> {
match have_ingredients(food) {
None => None,
Some(food) => match have_recipe(food) {
None => None,
Some(food) => Some(food),
},
}
}

// 也可以使用 `and_then()` 把上面的逻辑改写得更紧凑:
fn cookable_v2(food: Food) -> Option<Food> {
have_ingredients(food).and_then(have_recipe)
}

fn eat(food: Food, day: Day) {
match cookable_v2(food) {
Some(food) => println!("Yay! On {:?} we get to eat {:?}.", day, food),
None => println!("Oh no. We don't get to eat on {:?}?", day),
}
}

fn main() {
let (cordon_bleu, steak, sushi) = (Food::CordonBleu, Food::Steak, Food::Sushi);

eat(cordon_bleu, Day::Monday);
eat(steak, Day::Tuesday);
eat(sushi, Day::Wednesday);
}

结果Result

  • Result相当于Option的进化版本,可以有可能的错误 而不仅仅是不存在
  • 他有两个结果:
1
2
Ok<T>:找到T元素
Err<E>:找到E元素,E元素是错误的类型。
  • Result也有类似Option的方法,如unwrap(),也有类似的组合算子。
  • Resultparse()方法,可以将字符串转换为想要的类型,但不一定总是成功,也可能失败。
  • parse()方法通常和unwrap()连接,以来达到处理错误的过程。
  • parse()使用FromStr trait实现。

Result 的 map

  • 使用间接地方法可以知道Result输入错误时知道parse或者其他类型的返回详情。
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
use std::num::ParseIntError;

// 就像 `Option` 那样,我们可以使用 `map()` 之类的组合算子。
// 除去写法外,这个函数与上面那个完全一致,它的作用是:
// 如果值是合法的,计算其乘积,否则返回错误。
fn multiply(first_number_str: &str, second_number_str: &str) -> Result<i32, ParseIntError> {
first_number_str.parse::<i32>().and_then(|first_number| {
second_number_str.parse::<i32>().map(|second_number| first_number * second_number)
})
}

fn print(result: Result<i32, ParseIntError>) {
match result {
Ok(n) => println!("n is {}", n),
Err(e) => println!("Error: {}", e),
}
}

fn main() {
// 这种情况下仍然会给出正确的答案。
let twenty = multiply("10", "2");
print(twenty);

// 这种情况下就会提供一条更有用的错误信息。
let tt = multiply("t", "2");
print(tt);
}

给Result起别名

1
2
// 为带有错误类型 `ParseIntError` 的 `Result` 定义一个泛型别名。
type AliasedResult<T> = Result<T, ParseIntError>;

提前返回

  • 使用match控制流和提前返回也可以改善处理错误的方法。
  • 具体的方法是使用result返回Err即可。

引入?

  • 遇到只想返回Err而不想返回panic的情况之下,使用?效果较好。

try! 宏

  • 旧的Rust代码中使用try!语句,它的作用和?相同。

处理多种错误类型

  • 当多种不同的错误类型需要进行交互时,如Option和Result,不同的Result之间进行交互。

从Option中取出Result

  • 让Option和Result相互包含,如Option中嵌套Result:
1
Option<Result<i32,ParseIntError>>
  • 或者使用组合算子进行交换ResultOption,这通常出现在?输出错误的时候继续处理。
  • 组合算子可以在Rust标准库中学习使用。

自定义错误类型

  • 实现Error trait和Display。
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
use std::error;
use std::fmt;

type Result<T> = std::result::Result<T, DoubleError>;

#[derive(Debug, Clone)]
// 定义我们的错误类型,这种类型可以根据错误处理的实际情况定制。
// 我们可以完全自定义错误类型,也可以在类型中完全采用底层的错误实现,
// 也可以介于二者之间。
struct DoubleError;

// 错误的生成与它如何显示是完全没关系的。没有必要担心复杂的逻辑会导致混乱的显示。
//
// 注意我们没有储存关于错误的任何额外信息,也就是说,如果不修改我们的错误类型定义的话,
// 就无法指明是哪个字符串解析失败了。
impl fmt::Display for DoubleError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid first item to double")
}
}

// 为 `DoubleError` 实现 `Error` trait,这样其他错误可以包裹这个错误类型。
impl error::Error for DoubleError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
// 泛型错误,没有记录其内部原因。
None
}
}

fn double_first(vec: Vec<&str>) -> Result<i32> {
vec.first()
// 把错误换成我们的新类型。
.ok_or(DoubleError)
.and_then(|s| {
s.parse::<i32>()
// 这里也换成新类型。
.map_err(|_| DoubleError)
.map(|i| 2 * i)
})
}

fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first doubled is {}", n),
Err(e) => println!("Error: {}", e),
}
}

fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];

print(double_first(numbers));
print(double_first(empty));
print(double_first(strings));
}

把错误装箱

  • 这种方法可以分辨错误类型,但是只有在函数运行过程了解,而不能静态判别。
  • 标准库通过from实现了Box到Box的转换。
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
41
42
43
44
45
46
47
48
49
50
51
52
53
use std::error;
use std::fmt;

// 为 `Box<error::Error>` 取别名。
type Result<T> = std::result::Result<T, Box<dyn error::Error>>;

#[derive(Debug, Clone)]
struct EmptyVec;

impl fmt::Display for EmptyVec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid first item to double")
}
}

impl error::Error for EmptyVec {
fn description(&self) -> &str {
"invalid first item to double"
}

fn cause(&self) -> Option<&dyn error::Error> {
// 泛型错误。没有记录其内部原因。
None
}
}

fn double_first(vec: Vec<&str>) -> Result<i32> {
vec.first()
.ok_or_else(|| EmptyVec.into()) // 装箱
.and_then(|s| {
s.parse::<i32>()
.map_err(|e| e.into()) // 装箱
.map(|i| 2 * i)
})
}

fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first doubled is {}", n),
Err(e) => println!("Error: {}", e),
}
}

fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];

print(double_first(numbers));
print(double_first(empty));
print(double_first(strings));
}

?的其他用法

  • ?不仅仅是unwrapreturn Err(err),实际上是unwrap或者return Err(From::from(err)),也就是说如果错误可以转换成返回类型地方使用?,会自动转换成返回类型。
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
41
42
use std::error;
use std::fmt;

// 为 `Box<error::Error>` 取别名。
type Result<T> = std::result::Result<T, Box<dyn error::Error>>;

#[derive(Debug)]
struct EmptyVec;

impl fmt::Display for EmptyVec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid first item to double")
}
}

impl error::Error for EmptyVec {}

// 这里的结构和之前一样,但是这次没有把所有的 `Result` 和 `Option` 串起来,
// 而是使用 `?` 立即得到内部值。
fn double_first(vec: Vec<&str>) -> Result<i32> {
let first = vec.first().ok_or(EmptyVec)?;
let parsed = first.parse::<i32>()?;
Ok(2 * parsed)
}

fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first doubled is {}", n),
Err(e) => println!("Error: {}", e),
}
}

fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];

print(double_first(numbers));
print(double_first(empty));
print(double_first(strings));
}

包裹错误

  • 将错误装箱改成包裹到自己的错误类型之中。
  • 首先定义一个enum枚举中有两个结构体。
  • 然后采用内部各变量对Error trait的实现不同的方法再进行输出错误信息。

遍历Result

  • 使用filter_map()忽略失败的项
  • 使用collect()使整个操作失败
  • 使用Partiton()收集所有合法的值和错误