#[err_tree]
derive
only.Expand description
Implements a type as an error tree.
The struct must define Error
and be annotated with #[err_tree]
above
any attributes relying on a full field definition. The type must then be
internally constructed with Self::_tree
to capture extra error
information in a hidden field.
Any derive such as [Clone
] that relies on all fields being present must
occur after the #[err_tree]
macro. The _err_tree_pkg
field will
otherwise be added late and break the derivation.
§Self::_tree
This is an internal-use constructor that takes all struct fields in order.
Use #[track_caller]
on any functions calling Self::_tree
to store the
callsite correctly.
Open an issue or PR
if this hidden field degrades a struct’s API (aside from requiring a
constructor method).
§Example
use bare_err_tree::{err_tree, tree_unwrap};
#[err_tree]
#[derive(Debug)]
struct Foo {
num: i32,
}
impl Foo {
#[track_caller]
pub fn new(num: i32) -> Self {
Foo::_tree(num)
}
}
impl Error for Foo {
fn source(&self) -> Option<&(dyn Error + 'static)> {
...
}
}
impl Display for Foo {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
...
}
}
§Field Annotations
The macro needs annotations for underlying source fields.
§Single Item
§Collection
*_iter_err
works on any type with a .iter()
method returning its items.
tree_iter_err
: Mark a field as a collection ofErrTree
implementingError
s.dyn_iter_err
: Mark a field as a collection of genericError
s.
*_iter_err
does not allocate for arrays with a known length.
The derive_alloc
feature enables generation of allocating code to support
dynamically sized collections.
§Example
use bare_err_tree::{err_tree, tree_unwrap, AsErrTree, ErrTree};
#[err_tree]
#[derive(Debug)]
struct Foo {
#[dyn_err]
io_err: std::io::Error,
#[dyn_iter_err]
extra_io_errs: [std::io::Error; 5],
}
impl Foo {
#[track_caller]
pub fn new(io_err: std::io::Error, extra_io_errs: [std::io::Error; 5]) -> Self {
Foo::_tree(io_err, extra_io_errs)
}
}
impl Error for Foo {
fn source(&self) -> Option<&(dyn Error + 'static)> {
...
}
}
impl Display for Foo {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
...
}
}
fn main() {
// Make a Foo of all EOF errors
let eof_gen = || std::io::Error::from(std::io::ErrorKind::UnexpectedEof);
let err = Foo::new(eof_gen(), std::array::from_fn(|_| eof_gen()));
// Confirm exactly six sources from annotation
err.as_err_tree(&mut |tree| {
let sources = tree.sources();
assert_eq!(sources.count(), 6);
});
}
§Generating a Wrapper
#[err_tree(WRAPPER)]
will generate a wrapper struct for storing metadata.
Enums need this form, as a hidden field cannot be added to the enum.
WRAPPER
provides From
both ways and
Deref
/DerefMut
to be
maximally transparent.
Some derives are automatically re-derived for the wrapper; any other traits
that need to be implemented for the wrapper can be written manually.
§Wrapper automatic re-derives
Eq
, PartialEq
,
Ord
, PartialOrd
,
Clone
, Hash
,
Default
.
§Enum Example
use bare_err_tree::{err_tree, tree_unwrap};
// Generates `FooWrap<T: Debug>`
#[err_tree(FooWrap)]
#[derive(Debug)]
enum Foo<T: Debug> {
Val(T),
#[dyn_err]
Single(std::io::Error),
#[dyn_iter_err]
Many([std::io::Error; 5]),
}
impl<T: Debug> Error for Foo<T> {
fn source(&self) -> Option<&(dyn Error + 'static)> {
...
}
}
impl<T: Debug> Display for Foo<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
...
}
}
fn main() {
let wrapped = FooWrap::from(Foo::Val(8_i32));
assert!(matches!(*wrapped, Foo::Val(8_i32)));
}
§Full Usage Example:
use bare_err_tree::{err_tree, tree_unwrap};
#[err_tree]
#[derive(Debug)]
struct Foo {
#[dyn_err]
io_err: std::io::Error,
#[dyn_iter_err]
extra_io_errs: [std::io::Error; 5],
}
impl Foo {
#[track_caller]
pub fn new(io_err: std::io::Error, extra_io_errs: [std::io::Error; 5]) -> Self {
Foo::_tree(io_err, extra_io_errs)
}
}
impl Error for Foo {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.io_err)
}
}
impl Display for Foo {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
...
}
}
/// Always return the error with tree formatting support
pub fn always_fail() -> Result<(), Foo> {
Err(Foo::new(
...
))
}
const MAX_DEPTH: usize = 10;
const MAX_CHARS: usize = MAX_DEPTH * 6;
pub fn main() {
let result = always_fail();
/// Fancy display panic with a maximum tree depth of 10 errors
tree_unwrap::<MAX_CHARS, _, _>(result);
}