Effortlessly create, group, and nest arbitrary errors, and defer error handling ergonomically.
#[cfg(any(feature = "rust-v1.81", feature = "std"))]
use lazy_errors::{prelude::*, Result};
#[cfg(not(any(feature = "rust-v1.81", feature = "std")))]
use lazy_errors::surrogate_error_trait::{prelude::*, Result};
fn run(input: &[&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("1337").or_stash(&mut errs); // `errs` contains 1 errors
let numbers = input
.iter()
.map(|&text| -> Result<u8> {
u8::from_str(text)
// Make sure validation produces nicer error messages:
.or_wrap_with(|| format!("Input '{text}' is invalid"))
})
// Fail lazily after collecting all errors:
.try_collect_or_stash(&mut errs);
// If any item in `input` is invalid, we don't want to continue
// but return _all_ errors that have occurred so far.
let numbers: Vec<u8> = try2!(numbers);
println!("input = {numbers:?}");
u8::from_str("-1").or_stash(&mut errs);
errs.into() // `Ok(())` if `errs` is still empty, `Err` otherwise
}
fn main() {
let err = run(&["❓", "42", "❗"]).unwrap_err();
let n = err.children().len();
eprintln!("Got an error with {n} children.");
eprintln!("---------------------------------------------------------");
eprintln!("{err:#}");
}
Running the example will print:
Got an error with 3 children.
---------------------------------------------------------
There were one or more errors
- number too large to fit in target type
at src/main.rs:9:26
- Input '❓' is invalid: invalid digit found in string
at src/main.rs:16:18
at lazy_errors/src/try_collect_or_stash.rs:148:35
at lazy_errors/src/stash_err.rs:145:46
- Input '❗' is invalid: invalid digit found in string
at src/main.rs:16:18
at lazy_errors/src/try_collect_or_stash.rs:148:35
at lazy_errors/src/stash_err.rs:145:46
lazy_errors provides types, traits, and blanket implementations
on Result that can be used to ergonomically defer error handling.
lazy_errors allows you to easily create ad-hoc errors
as well as wrap a wide variety of errors in a single common error type,
simplifying your codebase.
In that latter regard, it is similar to anyhow/eyre,
except that its reporting isn’t as fancy or detailed (for example,
lazy_errors tracks source code file name and line numbers instead of
providing full std::backtrace support).
On the other hand, lazy_errors adds methods to Result
that let you continue on failure,
deferring returning Err results.
lazy_errors allows you to return two or more errors
from functions simultaneously and ergonomically.
lazy_errors also supports nested errors.
When you return nested errors from functions,
errors will form a tree while “bubbling up”.
You can report that error tree the user/developer in its entirety.
lazy_errors integrates with core::error::Error
and is #![no_std] by default.
By default, lazy_errors will box your error values (like anyhow/eyre),
which allows you to use different error types in the same Result type.
However, lazy_errors will respect static error type information
if you provide it explicitly.
If you do so, you can access fields and methods of your error values
at run-time without needing downcasts.
Both modes of operation can work together, as will be shown
in the example on the bottom of the page.
When you define a few simple type aliases,
lazy_errors also easily supports custom error types that aren’t
Sync or even Send.
Common reasons to use the lazy_errors crate are:
Result,
and want to return an error that wraps all errors that occurred.core::error::Error/Display/Debug/Send/Sync or other common
traits.std (disabled by default):std::error::Error (instead of
core::error::Error)std::error::Error for lazy_errors error types (instead of
core::error::Error)core::error::Error
was stabilized in Rust v1.81)eyre: Adds into_eyre_result and into_eyre_report conversionsrust-v$N (where $N is a Rust version number): Add support for error
types from core and alloc that were stabilized in the respective Rust
version.The MSRV of lazy_errors depends on the set of enabled features:
rust-v$N features where $N
is greater than the version of your Rust toolchain. For example, to
compile lazy_errors on Rust v1.69, you have to disable rust-v1.81 and
rust-v1.77, but not rust-v1.69.eyre needs at least Rust v1.65core::error::Error is not stable yet. If
you’re using a Rust version before v1.81, please consider enabling the
std feature to make lazy_errors use std::core::Error instead.lazy_errors can actually support any error type as long as it’s Sized;
it doesn’t even need to be Send or Sync. You only need to specify
the generic type parameters accordingly, as will be shown in the example
on the bottom of this page. Usually however, you’d want to use the
aliased types from the [prelude][__link0]. When you’re using these aliases,
errors will be boxed and you can dynamically return groups of errors
of differing types from the same function. When you’re also using
the default feature flags, lazy_errors is #![no_std] and
integrates with core::error::Error. In that case,
lazy_errors supports any error type that implements core::error::Error,
and all error types from this crate implement core::error::Error as well.
In Rust versions below v1.81, core::error::Error is not stable yet.
If you’re using an old Rust version, please disable (at least)
the rust-v1.81 feature and enable the std feature instead.
Enabling the std feature will make lazy_errors use std::error::Error
instead of core::error::Error. If you’re using an old Rust version and
need #![no_std] support nevertheless, please use the types from
the [surrogate_error_trait::prelude][__link1] instead of the regular prelude.
If you do so, lazy_errors will box any error type that implements the
[surrogate_error_trait::Reportable][__link2] marker trait.
If necessary, you can implement that trait for your custom types as well
(it’s just a single line).
While lazy_errors works standalone, it’s not intended to replace
anyhow or eyre. Instead, this project was started to explore
approaches on how to run multiple fallible operations, aggregate
their errors (if any), and defer the actual error handling/reporting
by returning all of these errors from functions that return Result.
Generally, Result<_, Vec<_>> can be used for this purpose,
which is not much different from what lazy_errors does internally.
However, lazy_errors provides “syntactic sugar”
to make this approach more ergonomic.
Thus, arguably the most useful method in this crate is [or_stash][__link3].
or_stash on [Result][__link4][or_stash][__link5] is arguably the most useful method of this crate.
It becomes available on Result as soon as you
import the [OrStash][__link6] trait or the [prelude][__link7].
Here’s an example:
#[cfg(any(feature = "rust-v1.81", feature = "std"))]
use lazy_errors::{prelude::*, Result};
#[cfg(not(any(feature = "rust-v1.81", 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"});
}
In the example above, run() will print 42, run cleanup(),
and then return the stashed errors.
Note that the [ErrorStash][__link8] is created manually in the example above.
The [ErrorStash][__link9] is empty before the first error is added.
Converting an empty [ErrorStash][__link10] to [Result][__link11] will produce Ok(()).
When [or_stash][__link12] is called on Result::Err(e),
e will be moved into the [ErrorStash][__link13]. As soon as there is
at least one error stored in the [ErrorStash][__link14], converting [ErrorStash][__link15]
into [Result][__link16] will yield a Result::Err that contains an [Error][__link17],
the main error type from this crate.
or_create_stash on [Result][__link18]Sometimes you don’t want to create an empty [ErrorStash][__link19] beforehand.
In that case you can call [or_create_stash][__link20] on Result
to create a non-empty container on-demand, whenever necessary.
When [or_create_stash][__link21] is called on Result::Err, the error
will be put into a [StashWithErrors][__link22] instead of an [ErrorStash][__link23].
[ErrorStash][__link24] and [StashWithErrors][__link25] behave similarly.
While both [ErrorStash][__link26] and [StashWithErrors][__link27] can take additional
errors, a [StashWithErrors][__link28] is guaranteed to be non-empty.
The type system will be aware that there is at least one error.
Thus, while [ErrorStash][__link29] can only be converted into [Result][__link30],
yielding either Ok(()) or Err(e) (where e is [Error][__link31]),
this distinction allows converting [StashWithErrors][__link32] into [Error][__link33]
directly.
#[cfg(any(feature = "rust-v1.81", feature = "std"))]
use lazy_errors::{prelude::*, Result};
#[cfg(not(any(feature = "rust-v1.81", 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"});
}
stash_err on [Iterator][__link34]Quite similarly to calling [or_stash][__link35] on [Result][__link36],
you can call [stash_err][__link37] on [Iterator<Item = Result<T, E>>][__link38]
to turn it into Iterator<Item = T>,
moving any E item into an error stash as soon as they are encountered:
#[cfg(any(feature = "rust-v1.81", feature = "std"))]
use lazy_errors::{prelude::*, Result};
#[cfg(not(any(feature = "rust-v1.81", feature = "std")))]
use lazy_errors::surrogate_error_trait::{prelude::*, Result};
fn parse_input() -> Result<Vec<u8>> {
let mut errs = ErrorStash::new(|| "Invalid input");
let input = vec![Ok(1), Err("❓"), Ok(42), Err("❗")];
let numbers: Vec<u8> = input
.into_iter()
.stash_err(&mut errs)
.collect();
let err = errs.into_result().unwrap_err();
let msg = format!("{err}");
assert_eq!(msg, "Invalid input (2 errors)");
Ok(numbers)
}
let numbers = parse_input().unwrap();
assert_eq!(&numbers, &[1, 42]);
try_collect_or_stash on [Iterator][__link39][try_collect_or_stash][__link40] is a counterpart to [Iterator::try_collect][__link41]
from the Rust standard library that will not short-circuit,
but instead move all Err items into an error stash.
As explained above,
calling [stash_err][__link42] on [`Iterato
$ claude mcp add lazy_errors \
-- python -m otcore.mcp_server <graph>