题目来自:https://practice-zh.course.rs/result-panic/result.html
1
// 填空并修复错误
use std::num::ParseIntError;fn multiply(n1_str: &str, n2_str: &str) -> __ {let n1 = n1_str.parse::<i32>();let n2 = n2_str.parse::<i32>();Ok(n1.unwrap() * n2.unwrap())
}fn main() {let result = multiply("10", "2");assert_eq!(result, __);let result = multiply("t", "2");assert_eq!(result.__, 8);println!("Success!")
}
当你在IDE里敲下如上代码的时候,第一个问题就解决了:
unwrap
意味着遇到错误不再返回错误,而是当场panic,因此我们不能让它出现错误。
use std::num::ParseIntError;fn multiply(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {let n1 = n1_str.parse::<i32>();let n2 = n2_str.parse::<i32>();Ok(n1.unwrap() * n2.unwrap())
}fn main() {let result = multiply("10", "2");assert_eq!(result, Ok(20));let result = multiply("4", "2");assert_eq!(result.unwrap(), 8);println!("Success!")
}
2
use std::num::ParseIntError;// 使用 `?` 来实现 multiply
// 不要使用 unwrap !
fn multiply(n1_str: &str, n2_str: &str) -> __ {
}fn main() {assert_eq!(multiply("3", "4").unwrap(), 12);println!("Success!")
}
?
跟 unwrap
非常像,但是 ?
会返回一个错误,而不是直接 panic
.
fn multiply(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {let n1 = n1_str.parse::<i32>()?;let n2 = n2_str.parse::<i32>()?;Ok(n1 * n2)
}fn main() {assert_eq!(multiply("3", "4"), Ok(12));println!("Success!")
}
问号在C++
中大多用于三元表达式,Rust
删除了这一功能。此外,Rust
的这个问号设计显然借鉴了一些现代的语言(比如Kotilin
用问号代表变量可能为空,让编译器放弃空指针检查)
3
use std::fs::File;
use std::io::{self, Read};fn read_file1() -> 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),}
}// 填空
// 不要修改其它代码
fn read_file2() -> Result<String, io::Error> {let mut s = String::new();__;Ok(s)
}fn main() {assert_eq!(read_file1().unwrap_err().to_string(), read_file2().unwrap_err().to_string());println!("Success!")
}
其实就是将上面的写法转成问号表达式:
fn read_file2() -> Result<String, io::Error> {let mut s = String::new();let f = File::open("hello.txt")?.read_to_string(&mut s)?;Ok(s)
}
另外这个unwrap_err
,网上甚至都搜不到(Rust的普及还是任重道远),我看了下源代码的介绍,很有意思。
Returns the contained [
Err
] value, consuming theself
value.
Panics if the value is an [Ok
], with a custom panic message provided by the [Ok
]'s value.
源代码如下:
#[inline]#[track_caller]#[stable(feature = "rust1", since = "1.0.0")]pub fn unwrap_err(self) -> EwhereT: fmt::Debug,{match self {Ok(t) => unwrap_failed("called `Result::unwrap_err()` on an `Ok` value", &t),Err(e) => e,}}
和unwrap
刚好相反,unwrap_err
的调用者如果是Ok(msg)
,会panic
,msg
传入panic
打印出来;如果是Err(msg)
,则返回msg
示例如下:
let x: Result<u32, &str> = Ok(2);
x.unwrap_err(); // panics with `2`
let x: Result<u32, &str> = Err("emergency failure");
assert_eq!(x.unwrap_err(), "emergency failure");
4
use std::num::ParseIntError;// 使用两种方式填空: map, and then
fn add_two(n_str: &str) -> Result<i32, ParseIntError> {n_str.parse::<i32>().__
}fn main() {assert_eq!(add_two("4").unwrap(), 6);println!("Success!")
}
map
map
的源代码如下:
#[inline]#[stable(feature = "rust1", since = "1.0.0")]pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Result<U, E> {match self {Ok(t) => Ok(op(t)),Err(e) => Err(e),}}
用于在方法和返回Result之间执行某些操作。这些操作只会对正确的结果执行。
答案如下:
fn add_two(n_str: &str) -> Result<i32, ParseIntError> {n_str.parse::<i32>().map(|num| num+2)
}
and_then
and_then
的源代码如下:
#[inline]#[stable(feature = "rust1", since = "1.0.0")]#[rustc_confusables("flat_map", "flatmap")]pub fn and_then<U, F: FnOnce(T) -> Result<U, E>>(self, op: F) -> Result<U, E> {match self {Ok(t) => op(t),Err(e) => Err(e),}}
相比于map
,and_then
需要自行构造返回值,而map可以直接使用调用者的返回值。比如这里map直接返回num+2
,相当于一种“替换”操作,返回的时候会自动加壳Ok
;而and_then
不再使用调用者的返回值了,需要自行构造Ok
。这样会更加灵活,比如,也许你不需要Result
,需要一个自定义的结构啥的。
fn add_two(n_str: &str) -> Result<i32, ParseIntError> {n_str.parse::<i32>().and_then(|num| Ok(num + 2))
}
5
use std::num::ParseIntError;// 使用 Result 重写后,我们使用模式匹配的方式来处理,而无需使用 `unwrap`
// 但是这种写法实在过于啰嗦..
fn multiply(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {match n1_str.parse::<i32>() {Ok(n1) => {match n2_str.parse::<i32>() {Ok(n2) => {Ok(n1 * n2)},Err(e) => Err(e),}},Err(e) => Err(e),}
}// 重写上面的 `multiply` ,让它尽量简洁
// 提示:使用 `and_then` 和 `map`
fn multiply1(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {// 实现...
}fn print(result: Result<i32, ParseIntError>) {match result {Ok(n) => println!("n is {}", n),Err(e) => println!("Error: {}", e),}
}fn main() {let twenty = multiply1("10", "2");print(twenty);// 下面的调用会提供更有帮助的错误信息let tt = multiply("t", "2");print(tt);println!("Success!")
}
这个例子用and_then
和map就很勉强了,降低了一点啰嗦,但是没完全去除:
fn multiply1(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {n1_str.parse::<i32>().and_then(|n1| {n2_str.parse::<i32>().map(|n2|{n1*n2})})
}
最简单的办法还是之前提到的问号表达:
fn multiply1(n1_str: &str, n2_str: &str) -> Result<i32, ParseIntError> {let n1: i32 = n1_str.parse()?;let n2: i32 = n2_str.parse()?;Ok(n1 * n2)
}
6
use std::num::ParseIntError;// 填空
type __;// 使用上面的别名来引用原来的 `Result` 类型
fn multiply(first_number_str: &str, second_number_str: &str) -> Res<i32> {first_number_str.parse::<i32>().and_then(|first_number| {second_number_str.parse::<i32>().map(|second_number| first_number * second_number)})
}// 同样, 这里也使用了类型别名来简化代码
fn print(result: Res<i32>) {match result {Ok(n) => println!("n is {}", n),Err(e) => println!("Error: {}", e),}
}fn main() {print(multiply("10", "2"));print(multiply("t", "2"));println!("Success!")
}
有点类似C的typedef
和C++的using
:
type Res<i32> = Result<i32, ParseIntError>;