Module std::result

1.0.0 · source ·
Expand description

Result 类型的错误处理。

Result<T, E> 是用于返回和传播错误的类型。 它是一个带有变体的枚举,Ok(T),表示成功并包含一个值,而 Err(E) 表示错误并包含一个错误值。

enum Result<T, E> {
   Ok(T),
   Err(E),
}
Run

只要预期到错误并且可以恢复,函数就返回 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:?}"),
}
Run

在简单情况下,在 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();
Run

必须使用结果

使用返回值指示错误的一个常见问题是,很容易忽略返回值,从而无法处理错误。 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>;
}
Run

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");
Run

如果您确实将其写在 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");
Run

您可能还简单地宣称成功:

assert!(file.write_all(b"important message").is_ok());
Run

或者使用 ? 在调用栈中传播错误:

fn write_message() -> io::Result<()> {
    let mut file = File::create("valuable_data.txt")?;
    file.write_all(b"important message")?;
    Ok(())
}
Run

问号运算符,?

在编写调用许多返回 Result 类型的函数的代码时,错误处理可能很乏味。 问号运算符 ? 在调用栈中隐藏了一些传播错误的样板。

它将替换为:

use std::fs::File;
use std::io::prelude::*;
use std::io;

struct Info {
    name: String,
    age: i32,
    rating: i32,
}

fn write_info(info: &Info) -> io::Result<()> {
    // 尽早返回错误
    let mut file = match File::create("my_best_friends.txt") {
           Err(e) => return Err(e),
           Ok(f) => f,
    };
    if let Err(e) = file.write_all(format!("name: {}\n", info.name).as_bytes()) {
        return Err(e)
    }
    if let Err(e) = file.write_all(format!("age: {}\n", info.age).as_bytes()) {
        return Err(e)
    }
    if let Err(e) = file.write_all(format!("rating: {}\n", info.rating).as_bytes()) {
        return Err(e)
    }
    Ok(())
}
Run

有了这个:

use std::fs::File;
use std::io::prelude::*;
use std::io;

struct Info {
    name: String,
    age: i32,
    rating: i32,
}

fn write_info(info: &Info) -> io::Result<()> {
    let mut file = File::create("my_best_friends.txt")?;
    // 尽早返回错误
    file.write_all(format!("name: {}\n", info.name).as_bytes())?;
    file.write_all(format!("age: {}\n", info.age).as_bytes())?;
    file.write_all(format!("rating: {}\n", info.rating).as_bytes())?;
    Ok(())
}
Run

好多了!

? 结束表达式将导致 Ok 的展开值,除非结果为 Err,在这种情况下,Err 会从封闭的函数中提前返回。

? 可以用在返回 Result 的函数中,因为它提供了 Err 的提前返回。

方法概述

除了使用模式匹配,Result 还提供了多种不同的方法。

查询变体

如果 Result 分别为 OkErr,则 is_okis_err 方法返回 true

用于处理引用的适配器

  • 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>

提取包含的值

当它是 Ok 变体时,这些方法提取 Result<T, E> 中包含的值。如果 ResultErr

panicking 方法 expectunwrap 需要 E 来实现 Debug trait。

当它是 Err 变体时,这些方法提取 Result<T, E> 中包含的值。他们需要 T 来实现 Debug trait。如果 ResultOk

转换包含的值

这些方法将 Result 转换为 Option

此方法转换 Ok 变体中包含的值:

此方法转换 Err 变体中包含的值:

这些方法将 Result<T, E> 转换为可能不同类型 U 的值:

  • map_or 将提供的函数应用于 Ok 的包含值,或者如果 Result 是返回提供的默认值 Err
  • map_or_else applies the provided function to the contained value of Ok, or applies the provided default fallback function to the contained value of Err

布尔运算符

这些方法将 Result 视为布尔值,其中 Ok 的作用类似于 true,而 Err 的作用类似于 false。这些方法有两类:一类以 Result 作为输入,一类以函数作为输入 (延迟评估)。

andor 方法将另一个 Result 作为输入,并生成一个 Result 作为输出。and 方法可以生成具有与 Result<T, E> 不同的内部类型 UResult<U, E> 值。 or 方法可以生成具有与 Result<T, E> 不同的错误类型 FResult<T, F> 值。

methodselfinputoutput
andErr(e)(ignored)Err(e)
andOk(x)Err(d)Err(d)
andOk(x)Ok(y)Ok(y)
orErr(e)Err(d)Err(d)
orErr(e)Ok(y)Ok(y)
orOk(x)(ignored)Ok(x)

and_thenor_else 方法将函数作为输入,并且仅在需要产生新值时才评估函数。and_then 方法可以生成一个 [Result<U,E>] 值,该值的内部类型 U 与 [Result<T,E>] 不同。 or_else 方法可以生成具有与 Result<T, E> 不同的错误类型 FResult<T, F> 值。

methodselffunction inputfunction resultoutput
and_thenErr(e)(not provided)(not evaluated)Err(e)
and_thenOk(x)xErr(d)Err(d)
and_thenOk(x)xOk(y)Ok(y)
or_elseErr(e)eErr(d)Err(d)
or_elseErr(e)eOk(y)Ok(y)
or_elseOk(x)(not provided)(not evaluated)Ok(x)

比较运算符

如果 TE 都实现 PartialOrd,那么 Result<T, E> 将派生其 PartialOrd 实现。 按照此顺序,一个 Ok 的比较小于任何 Err,而两个 Ok 或两个 Err 的比较与其包含的值分别在 TE 中进行比较。 如果 TE 都实现了 Ord,那么 Result<T, E> 也实现了。

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);
Run

迭代 Result

可以对 Result 进行迭代。如果您需要一个条件为空的迭代器,这会很有帮助。迭代器要么产生单个值 (当 ResultOk 时),要么不产生任何值 (当 ResultErr 时)。 例如,如果 ResultOk(v),则 into_iter 的作用类似于 once(v); 如果 ResultErr,则它的作用类似于 empty()

Result<T, E> 上的迭代器分为三种类型:

  • into_iter 消耗 Result 并产生包含的值
  • iter 对包含的值产生类型为 &T 的不可变引用
  • iter_mut 产生一个 &mut T 类型的可变引用到包含的值

有关这如何有用的示例,请参见 迭代 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:?}");
Run

收集到 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]));
Run

Result 还实现了 ProductSum traits,允许对 Result 值的迭代器提供 productsum 方法。

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));
Run

Structs

Enums

  • Result 是代表成功 (Ok) 或失败 (Err) 的类型。