0%

Result、Option组合子的使用

  在最近的code review过程中,发现自己针对RustResult<T,E>处理过程中使用的map,and_then,map_err这些组合子理解不是很到位,今天经过查看API文档和运行demo程序,再次对这方面的知识进行学习。
  在Rust程序的编写过程中,会存在大量返回Option、Result这类的结果,若是使用match方式来处理会发现处理过程比较繁琐,需要针对不同的情况分别做处理,特别是在经过多层嵌套后会造成代码的可读性降低。为了简化这方面的问题我们可以使用map,and_then等这些组合子来使得我们的程序更具有可读性。关于错误处理的更多内容,可以参考文章。下面分别针对map,and_then,map_err来进行介绍

使用方式介绍

  通过在Rust API文档中搜索map这个关键词,我们会发现在很多地方都存在这个方法。这里我们查看Enum std::result::Result这个枚举里面的定义。通过文档我们知道map,and_then,map_err这类方法可以不用先将Result类型的值取出来,可以直接调用。

1
2
pub fn map<U, F>(self, op: F) -> Result<U, E> 
where F: FnOnce(T) -> U,

  根据map函数的签名,当Result<T,E>类型实例返回Ok结果时,使用map经过F作用后仅返回U类型,即将类型T转换为类型U。这里没有对U类型做任何说明,则表示U可以是任意类型,可以是普通类型,也可以是Result、Option这类更复杂的类型。假如在执行F这个闭包处理过程中出现Result类型结果,此时的U即为 Result类型,若是不继续处理这个结果, 最后的返回值为Result<Result<_,E>>;

1
2
3
pub fn and_then<U, F>(self, op: F) -> Result<U, E>
where
F: FnOnce(T) -> Result<U, E>,

  根据and_then函数的签名,知道使用and_then经过F作用后得到的结果为Result<U, E>,因此可以用来处理map 得到Result<Result<_,E>> 这种类型的结果(实现少一层result的效果),也可以直接来处理针对F要返回Result的情况。通过上述定义我们知道,当在表达式中连续使用map调用不会改变最初函数调用可能返回的Err类型,但是在使用and_then时会返回E,此时就会存在当经过多次and_then调用后,返回的Error类型不一样,造成代码编译不能通过。因此在and_then的使用过程中需要将每次调用可能产生的的Err类型转换为一致的Err类型;关于如何返回一致的错误类型,可以查看rust关于自定义错误类型的相关内容(后续补充这部分的文档)。

1
2
3
pub fn map_err<F, O>(self, op: O) -> Result<T, F>
where
O: FnOnce(E) -> F,

  通过Api文档我们知道map_err只是用来处理Err这种情况的。其中需要注意的是当map,and_then这些组合子在一个表达式中多次连续使用时,产生的Err可能存在多个来源,但是我们在使用map_err时只会调用一次,并且map_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
fn chain_match(num: &str) -> Result<u32, String> {
num.parse::<u32>()
.and_then(|first| {
let test_num1_str = format!("{}", first * 2);
println!("test_num1_str is:{}", test_num1_str);
test_num1_str.parse::<u32>()
})
.and_then(|data| {
let test_num2_str = format!("{}", data * 3);
println!("test_num2_str is:{}", test_num2_str);
test_num2_str.parse::<u32>()
})
.map_err(|err| {
println!("encounter a error,detail is:{} ", err.to_string());
err.to_string()
})
}

#[test]
fn chain_match_test() {
assert_eq!(Ok(60), chain_match("10"));
println!("---------------");
assert_eq!(Ok(60), chain_match("a"));
}

运行上述代码 我们得到以下结果

1
2
3
4
5
6
7
8
test_num1_str is:20
test_num2_str is:60
---------------
encounter a error,detail is:invalid digit found in string


Expected :Err("invalid digit found in string")
Actual :Ok(60)

  通过上述结果我们可以发现map_err处理的是第一次类型转换中产生的错误。针对后续两个and_then错误的处理,可以分别将test_num1_strtest_num1_str修改为类型转换会失败的情况,通过观察结果就可以发现map_err的处理细节。

总结

  在上述的内容介绍中主要是围绕Result来进行的,针对Option他们的处理过程是类似的就没有分别进行叙述。针对ResultOption中还有很多类似的组合子,他们实现的效果是类似的,通过查看API文档能够了解他们适用的场景。通过这篇文章的总结,算是对组合子的使用有了更深的理解,能够方便自己以后在复习这方面知识时知道自己的推导过程。