8个版本 (破坏性更新)
0.7.0 | 2024年7月9日 |
---|---|
0.6.0 | 2024年6月25日 |
0.5.0 | 2024年6月11日 |
0.4.0 | 2024年6月8日 |
0.1.1 | 2024年5月24日 |
432 在 Rust模式 中
每月下载量:57
150KB
1.5K SLoC
lazy_errors
轻松创建、分组和嵌套任意错误,并优雅地延迟错误处理。
#[cfg(feature = "std")]
use lazy_errors::{prelude::*, Result};
#[cfg(not(feature = "std"))]
use lazy_errors::surrogate_error_trait::{prelude::*, Result};
fn run(input1: &str, input2: &str) -> Result<()>
{
let mut errs = ErrorStash::new(|| "There were one or more errors");
u8::from_str("42").or_stash(&mut errs); // `errs` contains 0 errors
u8::from_str("❌").or_stash(&mut errs); // `errs` contains 1 error
u8::from_str("1337").or_stash(&mut errs); // `errs` contains 2 errors
// `input1` is very important in this example,
// so make sure it has a nice message.
let r: Result<u8> = u8::from_str(input1)
.or_wrap_with(|| format!("Input '{input1}' is invalid"));
// If `input1` is invalid, we don't want to continue
// but return _all_ errors that have occurred so far.
let input1: u8 = try2!(r.or_stash(&mut errs));
println!("input1 = {input1:#X}");
// Continue handling other `Result`s.
u8::from_str(input2).or_stash(&mut errs);
errs.into() // `Ok(())` if `errs` is still empty, `Err` otherwise
}
fn main()
{
let err = run("❓", "❗").unwrap_err();
let n = err.children().len();
eprintln!("Got an error with {n} children.");
eprintln!("---------------------------------------------------------");
eprintln!("{err:#}");
}
运行示例将打印
Got an error with 3 children.
---------------------------------------------------------
There were one or more errors
- invalid digit found in string
at src/main.rs:10:24
- number too large to fit in target type
at src/main.rs:11:26
- Input '❓' is invalid: invalid digit found in string
at src/main.rs:16:10
at src/main.rs:20:30
概要
lazy_errors
提供了类型、特性和对 Result
的泛型实现,可以用于优雅地延迟错误处理。 lazy_errors
允许你轻松创建临时错误以及将各种错误封装在单个通用错误类型中,简化了你的代码库。在这方面,它与 anyhow
/eyre
类似,但其报告方式不够花哨或详细(例如,lazy_errors
跟踪源代码文件名和行号,而不是提供完整的 std::backtrace
支持)。另一方面,lazy_errors
为 Result
添加了方法,让你在失败时继续执行,延迟返回 Err
结果。 lazy_error
还支持嵌套错误。当你从函数返回嵌套错误时,错误将形成一个树状结构并在“冒泡”。你可以完整地向用户/开发者报告该错误树。
默认情况下,lazy_errors
将将错误值装箱(如 anyhow
/eyre
),这允许你在相同的 Result
类型中使用不同的错误类型。然而,如果你明确提供了静态错误类型信息,lazy_errors
将会尊重它。如果你这样做,你可以在运行时访问错误值的字段和方法,而无需进行向下转换。这两种操作模式可以一起工作,如下面的示例所示。
虽然默认情况下 lazy_error
通过 std::error::Error
集成,但它也支持 #![no_std]
,如果你禁用了 std
功能。当你定义一些简单的类型别名时,lazy_errors
可以轻松支持那些不是 Sync
或甚至 Send
的错误类型。
使用此包的常见原因包括
- 你想要返回一个错误,但在返回之前运行一些可能失败的清理逻辑。
- 更普遍地,你正在调用两个或多个返回
Result
的函数,并希望返回一个包含所有已发生错误的错误。 - 你正在启动几个并行活动,等待它们的完成,并希望返回所有发生的错误。
- 在运行一些报告或恢复逻辑之前,你想要聚合多个错误,遍历收集到的所有错误。
- 你需要处理不实现
std::error::Error
/Display
/Debug
/Send
/Sync
或其他常见特质的错误。
特性标志
std
:- 支持实现
std::error::Error
的错误类型。 - 为
lazy_error
错误类型实现std::error::Error
。
- 支持实现
eyre
:添加into_eyre_result
和into_eyre_report
转换。rust-vN
(其中N
是 Rust 版本号):仅添加了对在相应 Rust 版本中稳定的core
和alloc
中的某些错误类型的支持。
MSRV
lazy_errors
的 MSRV 依赖于启用的功能集
- Rust 1.77 支持所有功能及其所有组合。
- Rust 版本 1.61 .. 1.77 需要你禁用所有
rust-vN
功能,其中N
大于你的 Rust 工具链版本。例如,要在 Rust 1.66 上编译lazy_errors
,你必须禁用rust-v1.77
和rust-v1.69
,但不是rust-v1.66
。 eyre
至少需要 Rust 1.65。- Rust 版本低于 1.61 不受支持。
教程
lazy_errors
实际上可以支持任何错误类型,只要它是 Sized
;它甚至不需要是 Send
或 Sync
。你只需要相应地指定泛型类型参数,如下页底部的示例所示。通常,你想要使用来自 prelude
的别名。当你使用这些别名时,错误将被装箱,你可以从同一函数动态返回不同类型的错误组。
默认情况下启用了 std
功能,使得 lazy_error
能够支持实现 std::error::Error
的第三方错误类型。在这种情况下,从这个crate中所有的错误类型都将实现 std::error::Error
。如果您需要 #![no_std]
支持,您可以禁用 std
功能并使用 surrogate_error_trait::prelude
代替。如果您这样做,lazy_errors
将将任何实现 surrogate_error_trait::Reportable
标记特质的错误类型进行装箱。如果需要,您也可以为您的自定义类型实现该特质(只需一行代码)。
lazy_errors
可以独立工作,但它并不打算取代 anyhow
或 eyre
。相反,这个项目开始是为了探索如何运行多个可能出错的操作,聚合它们(如果有)的错误,并通过从返回 Result
的函数中返回所有这些错误来延迟实际的错误处理/报告。通常,Result<_, Vec<_>>
可以用于此目的,这与 lazy_errors
内部所做的并没有太大的区别。然而,lazy_errors
提供了“语法糖”,使这种方法更加方便。因此,可以说这个crate中最有用的方法是 or_stash
。
示例: or_stash
or_stash
可以说是这个crate中最有用的方法。一旦您导入了 OrStash
特质或 prelude
,它就会在 Result
上可用。以下是一个示例
#[cfg(feature = "std")]
use lazy_errors::{prelude::*, Result};
#[cfg(not(feature = "std"))]
use lazy_errors::surrogate_error_trait::{prelude::*, Result};
fn run() -> Result<()>
{
let mut stash = ErrorStash::new(|| "Failed to run application");
print_if_ascii("❓").or_stash(&mut stash);
print_if_ascii("❗").or_stash(&mut stash);
print_if_ascii("42").or_stash(&mut stash);
cleanup().or_stash(&mut stash); // Runs regardless of earlier errors
stash.into() // `Ok(())` if the stash was still empty
}
fn print_if_ascii(text: &str) -> Result<()>
{
if !text.is_ascii() {
return Err(err!("Input is not ASCII: '{text}'"));
}
println!("{text}");
Ok(())
}
fn cleanup() -> Result<()>
{
Err(err!("Cleanup failed"))
}
fn main()
{
let err = run().unwrap_err();
let printed = format!("{err:#}");
let printed = replace_line_numbers(&printed);
assert_eq!(printed, indoc::indoc! {"
Failed to run application
- Input is not ASCII: '❓'
at src/lib.rs:1234:56
at src/lib.rs:1234:56
- Input is not ASCII: '❗'
at src/lib.rs:1234:56
at src/lib.rs:1234:56
- Cleanup failed
at src/lib.rs:1234:56
at src/lib.rs:1234:56"});
}
在上面的示例中,run()
将打印 42
,运行 cleanup()
,然后返回存储的错误。
请注意,在上面的例子中,ErrorStash
是手动创建的。在添加第一个错误之前,ErrorStash
是空的。将空的 ErrorStash
转换为 Result
将产生 Ok(())
。当在 Result::Err(e)
上调用 or_stash
时,e
将被移动到 ErrorStash
中。一旦在 ErrorStash
中存储了至少一个错误,将 ErrorStash
转换为 Result
将产生一个包含 Error
的 Result::Err
,这是该包的主要错误类型。
示例:or_create_stash
有时候,你不想提前创建一个空的ErrorStash
。在这种情况下,你可以在Result
上调用or_create_stash
来按需创建一个非空容器, whenever necessary。当在Result::Err
上调用or_create_stash
时,错误将放入一个StashWithErrors
,而不是一个ErrorStash
。 ErrorStash
和StashWithErrors
的行为类似。虽然两者都可以接受额外的错误,但StashWithErrors
保证是非空的。类型系统会意识到至少有一个错误。因此,虽然ErrorStash
只能转换为Result
,产生Ok(())
或Err(e)
(其中e
是Error
),但这种区分允许直接将StashWithErrors
转换为Error
。
#[cfg(feature = "std")]
use lazy_errors::{prelude::*, Result};
#[cfg(not(feature = "std"))]
use lazy_errors::surrogate_error_trait::{prelude::*, Result};
fn run() -> Result<()>
{
match write("❌").or_create_stash(|| "Failed to run application") {
Ok(()) => Ok(()),
Err(mut stash) => {
cleanup().or_stash(&mut stash);
Err(stash.into())
},
}
}
fn write(text: &str) -> Result<()>
{
if !text.is_ascii() {
return Err(err!("Input is not ASCII: '{text}'"));
}
Ok(())
}
fn cleanup() -> Result<()>
{
Err(err!("Cleanup failed"))
}
fn main()
{
let err = run().unwrap_err();
let printed = format!("{err:#}");
let printed = replace_line_numbers(&printed);
assert_eq!(printed, indoc::indoc! {"
Failed to run application
- Input is not ASCII: '❌'
at src/lib.rs:1234:56
at src/lib.rs:1234:56
- Cleanup failed
at src/lib.rs:1234:56
at src/lib.rs:1234:56"});
}
示例:into_eyre_*
ErrorStash
和StashWithErrors
可以分别转换为Result
和Error
。对于eyre::Result
和eyre::Error
(即eyre::Report
),存在从ErrorStash
和StashWithErrors
到它们的类似但损失性的转换,分别是into_eyre_result
和into_eyre_report
。
use lazy_errors::prelude::*;
use eyre::bail;
fn run() -> Result<(), eyre::Report>
{
let r = write("❌").or_create_stash::<Stashable>(|| "Failed to run");
match r {
Ok(()) => Ok(()),
Err(mut stash) => {
cleanup().or_stash(&mut stash);
bail!(stash.into_eyre_report());
},
}
}
fn write(text: &str) -> Result<(), Error>
{
if !text.is_ascii() {
return Err(err!("Input is not ASCII: '{text}'"));
}
Ok(())
}
fn cleanup() -> Result<(), Error>
{
Err(err!("Cleanup failed"))
}
fn main()
{
let err = run().unwrap_err();
let printed = format!("{err:#}");
let printed = replace_line_numbers(&printed);
assert_eq!(printed, indoc::indoc! {"
Failed to run
- Input is not ASCII: '❌'
at src/lib.rs:1234:56
at src/lib.rs:1234:56
- Cleanup failed
at src/lib.rs:1234:56
at src/lib.rs:1234:56"});
}
示例:层次结构
你可能已经注意到,错误Error
形成层次结构
#[cfg(feature = "std")]
use lazy_errors::{prelude::*, Result};
#[cfg(not(feature = "std"))]
use lazy_errors::surrogate_error_trait::{prelude::*, Result};
fn parent() -> Result<()>
{
let mut stash = ErrorStash::new(|| "In parent(): child() failed");
stash.push(child().unwrap_err());
stash.into()
}
fn child() -> Result<()>
{
let mut stash = ErrorStash::new(|| "In child(): There were errors");
stash.push("First error");
stash.push("Second error");
stash.into()
}
fn main()
{
let err = parent().unwrap_err();
let printed = format!("{err:#}");
let printed = replace_line_numbers(&printed);
assert_eq!(printed, indoc::indoc! {"
In parent(): child() failed
- In child(): There were errors
- First error
at src/lib.rs:1234:56
- Second error
at src/lib.rs:1234:56
at src/lib.rs:1234:56"});
}
上述示例可能看起来很笨拙。实际上,这个示例只是为了说明错误层次结构。在实际应用中,您不会编写这样的代码。相反,您可能会依赖 or_wrap
或 or_wrap_with
。
示例:包装
您可以使用 or_wrap
或 or_wrap_with
将任何可以转换为 内嵌错误类型 的 Error
的值进行包装,或者为错误附加一些上下文。
#[cfg(feature = "std")]
use lazy_errors::{prelude::*, Result};
#[cfg(not(feature = "std"))]
use lazy_errors::surrogate_error_trait::{prelude::*, Result};
fn run(s: &str) -> Result<u32>
{
parse(s).or_wrap_with(|| format!("Not an u32: '{s}'"))
}
fn parse(s: &str) -> Result<u32>
{
let r: Result<u32, core::num::ParseIntError> = s.parse();
// Wrap the error type “silently”:
// No additional message, just file location and wrapped error type.
r.or_wrap()
}
fn main()
{
let err = run("❌").unwrap_err();
let printed = format!("{err:#}");
let printed = replace_line_numbers(&printed);
assert_eq!(printed, indoc::indoc! {"
Not an u32: '❌': invalid digit found in string
at src/lib.rs:1234:56
at src/lib.rs:1234:56"});
}
示例:临时错误
err!
宏允许您格式化字符串,并将其转换为同时是一个临时 Error
。
#[cfg(feature = "std")]
use lazy_errors::prelude::*;
#[cfg(not(feature = "std"))]
use lazy_errors::surrogate_error_trait::prelude::*;
let pid = 42;
let err: Error = err!("Error in process {pid}");
您通常会找到临时错误是错误树中的叶子。然而,错误树可以具有几乎任何 内嵌错误类型 作为叶子。
支持的错误类型
prelude
模块导出常用的特性和 别名 类型。导入 lazy_errors::prelude::*
应该可以满足大多数用例。您可能还需要导入 lazy_errors::Result
。在 ![no_std]
模式或当 core::error::Error
不可用时,您可以使用 surrogate_error_trait::prelude
来代替,并使用相应的 lazy_errors::surrogate_error_trait::Result
。
当您使用预定义中的别名类型时,如果 E
实现了 Into<Stashable>
,则这个库应支持任何 Result<_, E>
。基本上,Stashable
是一个 Box<dyn E>
,其中 E
要么是 std::error::Error
,要么是在 #![no_std]
模式下的代理 trait(《surrogate_error_trait::Reportable
))。因此,使用预定义中的别名类型,您放入此库定义的任何容器中的任何错误都将被装箱。选择 Into<Box<dyn E>>
特性约束,因为它为广泛的错误类型或“类似错误”类型实现了。满足此约束的类型的示例包括
&str
String
anyhow::Error
eyre::Report
std::error::Error
- 此库中所有的错误类型
此库的主要错误类型是 Error
。您可以通过调用 or_wrap
或 or_wrap_with
将所有受支持的“类似错误”类型转换为 Error
。
换句话说,这个包支持多种错误类型。然而,在某些情况下,你可能需要比这更灵活的处理方式。例如,你可能不希望丢失静态错误类型信息,或者你的错误类型不是Sync
。通常,只要E
实现了Into<I>
,这个包就能很好地与任何Result<_, E>
一起工作,其中I
是内层错误类型 of Error
。这个包会将其容器中的错误存储为类型I
,例如在ErrorStash
或Error
中。当你在使用prelude
中的类型别名时,I
总是Stashable
。然而,你并不需要使用Stashable
。你可以随意选择用于I
的类型。它可以是自定义类型,并且不需要实现任何特质或自动特质,除了Sized
。因此,如果预定义的别名不适用于你的目的,你可以手动导入所需的特性和类型,并定义自定义别名,如下一个示例所示。
示例:自定义错误类型
以下是一个复杂的示例,它不使用prelude
,而是定义了自己的别名。在示例中,Error<CustomError>
和ParserErrorStash
不对其错误进行装箱。相反,它们将所有错误类型信息以静态形式呈现,这允许你编写恢复逻辑而无需在运行时依赖向下转换。示例还展示了如何使用具有自定义生命周期的自定义错误类型与装箱错误类型(Stashable
)一起使用。
use lazy_errors::{err, ErrorStash, OrStash, StashedResult};
#[cfg(feature = "std")]
use lazy_errors::Stashable;
#[cfg(not(feature = "std"))]
use lazy_errors::surrogate_error_trait::Stashable;
#[derive(thiserror::Error, Debug)]
pub enum CustomError<'a>
{
#[error("Input is empty")]
EmptyInput,
#[error("Input '{0}' is not u32")]
NotU32(&'a str),
}
// Use `CustomError` as inner error type `I` for `ErrorStash`:
type ParserErrorStash<'a, F, M> = ErrorStash<F, M, CustomError<'a>>;
// Allow using `CustomError` as `I` but use `Stashable` by default:
pub type Error<I = Stashable<'static>> = lazy_errors::Error<I>;
fn main()
{
let err = run(&["42", "0xA", "f", "oobar", "3b"]).unwrap_err();
eprintln!("{err:#}");
}
fn run<'a>(input: &[&'a str]) -> Result<(), Error<Stashable<'a>>>
{
let mut errs = ErrorStash::new(|| "Application failed");
let parser_result = parse(input); // Soft errors
if let Err(e) = parser_result {
println!("There were errors.");
println!("Errors will be returned after showing some suggestions.");
let recovery_result = handle_parser_errors(&e); // Hard errors
errs.push(e);
if let Err(e) = recovery_result {
errs.push(e);
return errs.into();
}
}
// ... some related work, such as writing log files ...
errs.into()
}
fn parse<'a>(input: &[&'a str]) -> Result<(), Error<CustomError<'a>>>
{
if input.is_empty() {
return Err(Error::wrap(CustomError::EmptyInput));
}
let mut errs = ParserErrorStash::new(|| {
"Input has correctable or uncorrectable errors"
});
println!("Step #1: Starting...");
let mut parsed = vec![];
for s in input {
println!("Step #1: Trying to parse '{s}'");
// Ignore “soft” errors for now...
if let StashedResult::Ok(k) = parse_u32(s).or_stash(&mut errs) {
parsed.push(k);
}
}
println!(
"Step #1: Done. {} of {} inputs were u32 (decimal or hex): {:?}",
parsed.len(),
input.len(),
parsed
);
errs.into() // Return list of all parser errors, if any
}
fn handle_parser_errors(errs: &Error<CustomError>) -> Result<(), Error>
{
println!("Step #2: Starting...");
for e in errs.children() {
match e {
CustomError::NotU32(input) => guess_hex(input)?,
other => return Err(err!("Internal error: {other}")),
};
}
println!("Step #2: Done");
Ok(())
}
fn parse_u32(s: &str) -> Result<u32, CustomError>
{
s.strip_prefix("0x")
.map(|hex| u32::from_str_radix(hex, 16))
.unwrap_or_else(|| u32::from_str(s))
.map_err(|_| CustomError::NotU32(s))
}
fn guess_hex(s: &str) -> Result<u32, Error>
{
match u32::from_str_radix(s, 16) {
Ok(v) => {
println!("Step #2: '{s}' is not u32. Did you mean '{v:#X}'?");
Ok(v)
},
Err(e) => {
println!("Step #2: '{s}' is not u32. Aborting program.");
Err(err!("Unsupported input '{s}': {e}"))
},
}
}
运行上面的示例将产生类似以下输出
stdout:
Step #1: Starting...
Step #1: Trying to parse '42'
Step #1: Trying to parse '0xA'
Step #1: Trying to parse 'f'
Step #1: Trying to parse 'oobar'
Step #1: Trying to parse '3b'
Step #1: Done. 2 of 5 inputs were u32 (decimal or hex): [42, 10]
There were errors.
Errors will be returned after showing some suggestions.
Step #2: Starting...
Step #2: 'f' is not u32. Did you mean '0xF'?
Step #2: 'oobar' is not u32. Aborting program.
stderr:
Application failed
- Input has correctable or uncorrectable errors
- Input 'f' is not u32
at src/lib.rs:72:52
- Input 'oobar' is not u32
at src/lib.rs:72:52
- Input '3b' is not u32
at src/lib.rs:72:52
at src/lib.rs:43:14
- Unsupported input 'oobar': invalid digit found in string
at src/lib.rs:120:17
at src/lib.rs:45:18
许可证
根据您的选择,许可协议为以下之一
- Apache License,版本 2.0(LICENSE-APACHE或https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT许可证(LICENSE-MIT或http://opensource.org/licenses/MIT)
。
贡献
除非你明确声明,否则根据Apache-2.0许可证定义的任何有意提交以包含在作品中的贡献,均应如上双许可,无需任何额外的条款或条件。