Module std::result
Result<T, E>也是一个特殊的枚举,用来表达一个结果可能成功,也可能失败。
Result在标准库中的定义如下:
pub enum Result<T, E> {
Ok(T),
Err(E),
}泛型参数
- T:当结果为Ok时,返回的值的类型
- E:当结果为Err时,错误信息的类型
它有两个变体:
Ok(T)表示操作成功,携带一个结果值T。Err(E)表示操作失败,携带一个错误信息E。
只要预期到错误并且可以恢复,函数就返回 Result。在 std crate 中,Result 最主要用于 I/O。
返回 Result 的简单函数可以像这样定义和使用:
#[derive(Debug)]
enum Version { Version1, Version2 }
fn parse_version(header: &[u8]) -> Result<Version, &'static str> {
match header.get(0) {
None => Err("invalid header length"),
Some(&1) => Ok(Version::Version1),
Some(&2) => Ok(Version::Version2),
Some(_) => Err("invalid version"),
}
}
let version = parse_version(&[1, 2, 3, 4]);
match version {
Ok(v) => println!("working with version: {v:?}"),
Err(e) => println!("error parsing header: {e:?}"),
}在简单情况下,在 Result 上进行模式匹配非常简单明了,但是 Result 附带了一些方便的方法,使使用它更加简洁。
let good_result: Result<i32, i32> = Ok(10);
let bad_result: Result<i32, i32> = Err(10);
// `is_ok` 和 `is_err` 方法按照他们说的做。
assert!(good_result.is_ok() && !good_result.is_err());
assert!(bad_result.is_err() && !bad_result.is_ok());
// `map` 消耗 `Result` 并产生另一个。
let good_result: Result<i32, i32> = good_result.map(|i| i + 1);
let bad_result: Result<i32, i32> = bad_result.map(|i| i - 1);
// 使用 `and_then` 继续计算。
let good_result: Result<bool, i32> = good_result.and_then(|i| Ok(i == 11));
// 使用 `or_else` 处理该错误。
let bad_result: Result<i32, i32> = bad_result.or_else(|i| Ok(i + 20));
// 消费结果并用 `unwrap` 返回内容。
let final_awesome_result = good_result.unwrap();1.基本使用
Result<T, E> 枚举被包含在了 std::prelude(prelude 属于 Rust 标准库,Rust 会将最常用的类型、函数等提前引入其中,省得我们再手动引入)之中,你不需要将其显式引入作用域。
另外,它的成员 Ok 和 Err 也是如此,无需使用 Result:: 前缀就可直接使用 Ok 和 Err。
比如下面一段代码
let o: Result<i32, &str> = Result::Ok(123);
let e: Result<i32, &str> = Result::Err("Failed to read file");完全可以去掉Option::,然后使用下面的方式
let o: Result<i32, &str> = Ok(123);
let e: Result<i32, &str> = Err("Failed to read file");Result常用于一个io操作函数的返回值
fn read_file(path: &str) -> Result<String, &str> {
match std::fs::read_to_string(path) {
Ok(s) => Ok(s),
Err(_) => Err("Failed to read file"),
}
}注意
不管是定义一个Ok变量,还是Err变量,都需要显示声明泛型
let o: Result<i32, &str> = Result::Ok(123);
let e: Result<i32, &str> = Err("Failed to read file");2.模式匹配
对于一个Result,使用模式匹配来获取其结果是相当经典的
let o: Result<i32, &str> = Ok(123);
match o {
Ok(value) => {
println!("{}", value);
}
Err(err) => {
println!("{}", err);
}
}- 若Result的结果为Ok,那么在match中,Ok包含的值就会被赋值给参数value
- 若Result的结果为Err,那么在match中,Err包含的值就会被赋值给参数err
2.1 if let
if let是match的语法糖,简化Option和Result的match语句先看如下代码
当我们只关注Result成功的结果,而不想去处理失败的结果时,使用if let是个好选择
let o: Result<i32, &str> = Ok(123);
if let Ok(x) = o {
println!("{}", x);
}if let语句将先判断变量是否是Ok变体,如果是,则把这个变量的值赋值给Ok的参数
3.必须使用Result
使用返回值指示错误的一个常见问题是,很容易忽略返回值,从而无法处理错误。 Result 与 #[must_use] 属性一起注解,当忽略 Result 值时会导致编译器发出警告。 这使得 Result 对于可能遇到错误但不会返回有用值的函数特别有用。
考虑 Write trait 为 I/O 类型定义的 write_all 方法:
use std::io;
trait Write {
fn write_all(&mut self, bytes: &[u8]) -> Result<(), io::Error>;
}Note: Write 的实际定义使用了 io::Result,它只是 Result<T, io::Error> 的同义词。
该方法不会产生值,但是写入可能会失败。处理错误情况至关重要,并且 不要 编写类似以下内容的代码:
use std::fs::File;
use std::io::prelude::*;
let mut file = File::create("valuable_data.txt").unwrap();
// 如果 `write_all` 错误,那么我们将永远不会知道,因为返回值将被忽略。
//
file.write_all(b"important message");如果您确实将其写在 Rust 中,则编译器将向您发出警告 (默认情况下,由 unused_must_use lint 控制)。
相反,如果您不想处理该错误,则可以断言 expect 成功。 如果写入失败,这将为 panic,提供了一条边际有用的消息,指出原因:
use std::fs::File;
use std::io::prelude::*;
let mut file = File::create("valuable_data.txt").unwrap();
file.write_all(b"important message").expect("failed to write message");您可能还简单地宣称成功:
assert!(file.write_all(b"important message").is_ok());或者使用 ? 在调用栈中传播错误:
fn write_message() -> io::Result<()> {
let mut file = File::create("valuable_data.txt")?;
file.write_all(b"important message")?;
Ok(())
}4.问号运算符?
?运算符:是Rust 中用于错误处理的简洁语法,主要作用是从函数中提前返回错误
- 它可以用在返回 Result或Option 类型的函数中
- 成功时:解包
Ok(T)或Some(T)中的值,程序继续执行 - 失败时:提早返回
Err(E)或None,错误值会自动转换(若可能)
- 成功时:解包
当一个返回Result的函数中使用了另一个返回Result的函数,使用?运算符可以减少match语句的使用
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
Err("除数不能为0".to_string())
} else {
Ok(a / b)
}
}
fn get_divide(a: i32, b: i32) -> Result<i32, String> {
divide(a, b)?;
Ok(a / b)
}
fn main() {
let res = get_divide(500, 100);
match res {
Ok(d) => println!("{}", d),
Err(s) => println!("{}", s),
}
}5.方法概述
除了使用模式匹配,Result 还提供了多种不同的方法。
5.1 查询变体
如果 Result 分别为 Ok 或 Err,则 is_ok 和 is_err 方法返回 true。
5.2 用于处理引用的适配器
as_ref从&Result<T, E>转换为Result<&T, &E>as_mut从&mut Result<T, E>转换为Result<&mut T, &mut E>as_deref从&Result<T, E>转换为Result<&T::Target, &E>as_deref_mut从&mut Result<T, E>转换为Result<&mut T::Target, &mut E>
5.3 提取包含的值
当它是 Ok 变体时,这些方法提取 Result 中包含的值。如果 Result 是 Err:
expectpanics 带有提供的自定义消息unwrappanics 带有泛型信息unwrap_or返回提供的默认值unwrap_or_default返回类型T的默认值 (必须实现Defaulttrait)unwrap_or_else返回对提供的函数求值的结果
panicking 方法 expect 和 unwrap 需要 E 来实现 Debug trait。
当它是 Err 变体时,这些方法提取 Result 中包含的值。他们需要 T 来实现 Debug trait。如果 Result 是 Ok:
expect_errpanics 带有提供的自定义消息unwrap_errpanics 带有泛型信息
5.4 转换包含的值
errtransformsResultintoOption, mappingErr(e)toSome(e)andOk(v)toNoneoktransformsResultintoOption, mappingOk(v)toSome(v)andErr(e)toNonetransposetransposes aResultof anOptioninto anOptionof aResult
此方法转换 Ok 变体中包含的值:
此方法转换 Err 变体中包含的值:
这些方法将 Result 转换为可能不同类型 U 的值:
map_or将提供的函数应用于Ok的包含值,或者如果Result是返回提供的默认值Errmap_or_elseapplies the provided function to the contained value ofOk, or applies the provided default fallback function to the contained value ofErr
5.5 布尔运算符
这些方法将 Result 视为布尔值,其中 Ok 的作用类似于 true,而 Err 的作用类似于 false。这些方法有两类:一类以 Result 作为输入,一类以函数作为输入 (延迟评估)。
and 和 or 方法将另一个 Result 作为输入,并生成一个 Result 作为输出。and 方法可以生成具有与 Result 不同的内部类型 U 的 Result 值。 or 方法可以生成具有与 Result 不同的错误类型 F 的 Result 值。
| method | self | input | output |
|---|---|---|---|
and | Err(e) | (ignored) | Err(e) |
and | Ok(x) | Err(d) | Err(d) |
and | Ok(x) | Ok(y) | Ok(y) |
or | Err(e) | Err(d) | Err(d) |
or | Err(e) | Ok(y) | Ok(y) |
or | Ok(x) | (ignored) | Ok(x) |
and_then 和 or_else 方法将函数作为输入,并且仅在需要产生新值时才评估函数。and_then 方法可以生成一个 [Result<U,E>] 值,该值的内部类型 U 与 [Result<T,E>] 不同。 or_else 方法可以生成具有与 Result 不同的错误类型 F 的 Result 值。
| method | self | function input | function result | output |
|---|---|---|---|---|
and_then | Err(e) | (not provided) | (not evaluated) | Err(e) |
and_then | Ok(x) | x | Err(d) | Err(d) |
and_then | Ok(x) | x | Ok(y) | Ok(y) |
or_else | Err(e) | e | Err(d) | Err(d) |
or_else | Err(e) | e | Ok(y) | Ok(y) |
or_else | Ok(x) | (not provided) | (not evaluated) | Ok(x) |
5.6 比较运算符
如果 T 和 E 都实现 PartialOrd,那么 Result 将派生其 PartialOrd 实现。 按照此顺序,一个 Ok 的比较小于任何 Err,而两个 Ok 或两个 Err 的比较与其包含的值分别在 T 或 E 中进行比较。 如果 T 和 E 都实现了 Ord,那么 Result 也实现了。
assert!(Ok(1) < Err(0));
let x: Result<i32, ()> = Ok(0);
let y = Ok(1);
assert!(x < y);
let x: Result<(), i32> = Err(0);
let y = Err(1);
assert!(x < y);5.7 迭代 Result
可以对 Result 进行迭代。如果您需要一个条件为空的迭代器,这会很有帮助。迭代器要么产生单个值 (当 Result 为 Ok 时),要么不产生任何值 (当 Result 为 Err 时)。 例如,如果 Result 是 Ok(v),则 into_iter 的作用类似于 once(v); 如果 Result 是 Err,则它的作用类似于 empty()。
Result 上的迭代器分为三种类型:
有关这如何有用的示例,请参见 迭代 Option。
您可能希望使用迭代器链来执行可能失败的操作的多个实例,但希望在继续处理成功结果的同时忽略失败。 在本例中,我们利用 Result 的可迭代特性,使用 flatten 仅选择 Ok 值。
let mut results = vec![];
let mut errs = vec![];
let nums: Vec<_> = ["17", "not a number", "99", "-27", "768"]
.into_iter()
.map(u8::from_str)
// 保存原始 `Result` 值的克隆以进行检查
.inspect(|x| results.push(x.clone()))
// 挑战:解释这如何仅捕获 `Err` 值
.inspect(|x| errs.extend(x.clone().err()))
.flatten()
.collect();
assert_eq!(errs.len(), 3);
assert_eq!(nums, [17, 99]);
println!("results {results:?}");
println!("errs {errs:?}");
println!("nums {nums:?}");5.8 收集到 Result
Result 实现了 FromIterator trait,它允许将 Result 值上的迭代器收集到原始 Result 值的每个包含值的集合的 Result 中,或者如果任何元素是 Err,则为 Err。
let v = [Ok(2), Ok(4), Err("err!"), Ok(8)];
let res: Result<Vec<_>, &str> = v.into_iter().collect();
assert_eq!(res, Err("err!"));
let v = [Ok(2), Ok(4), Ok(8)];
let res: Result<Vec<_>, &str> = v.into_iter().collect();
assert_eq!(res, Ok(vec![2, 4, 8]));Result 还实现了 Product 和 Sum traits,允许对 Result 值的迭代器提供 product 和 sum 方法。
let v = [Err("error!"), Ok(1), Ok(2), Ok(3), Err("foo")];
let res: Result<i32, &str> = v.into_iter().sum();
assert_eq!(res, Err("error!"));
let v = [Ok(1), Ok(2), Ok(21)];
let res: Result<i32, &str> = v.into_iter().product();
assert_eq!(res, Ok(42));