/errgo

Generate enum variants inline

Primary LanguageRust

crates-io docs-rs github

errgo

Generate enum error variants inline.

A slightly type-safer take on anyhow, where each ad-hoc error is handleable by the caller. Designed to play nice with other crates like strum or thiserror.

This crate was written to aid wrapping C APIs - transforming e.g error codes to handleable messages. It shouldn't really be used for library api entry points - a well-considered top-level error type is likely to be both more readable and forward compatible. Consider reading Study of std::io::Error or simply making all generated structs pub(crate).

use errgo::errgo;

#[errgo]
fn shave_yaks(
    num_yaks: usize,
    empty_buckets: usize,
    num_razors: usize,
) -> Result<(), ShaveYaksError> {
    if num_razors == 0 {
        return Err(err!(NotEnoughRazors));
    }
    if num_yaks > empty_buckets {
        return Err(err!(NotEnoughBuckets {
            got: usize = empty_buckets,
            required: usize = num_yaks,
        }));
    }
    Ok(())
}

Under the hood, a struct like this is generated:

enum ShaveYaksError { // name and visibility are taken from function return type and visibility
    NotEnoughRazors,
    NotEnoughBuckets {
        got: usize,
        required: usize,
    }
}

Note that the struct definition is placed just above the function body, meaning that you can't use [errgo] on functions in impl blocks - you'll have to move the function body to an outer scope, and call it in the impl block.

Importantly, you can derive on the generated struct, and passthrough attributes, allowing you to use crates like thiserror or strum. See the [errgo] documentation for other arguments accepted by the macro.

#[errgo(derive(Debug, thiserror::Error))]
fn shave_yaks(
    num_yaks: usize,
    empty_buckets: usize,
    num_razors: usize,
) -> Result<(), ShaveYaksError> {
    if num_razors == 0 {
        return Err(err!(
            #[error("not enough razors!")]
            NotEnoughRazors
        ));
    }
    if num_yaks > empty_buckets {
        return Err(err!(
            #[error("not enough buckets - needed {required}")]
            NotEnoughBuckets {
                got: usize = empty_buckets,
                required: usize = num_yaks,
            }
        ));
    }
    Ok(())
}

Which generates the following:

#[derive(Debug, thiserror::Error)]
enum ShaveYaksError {
    #[error("not enough razors!")]
    NotEnoughRazors,
    #[error("not enough buckets - needed {required}")]
    NotEnoughBuckets {
        got: usize,
        required: usize,
    }
}

And err! macro invocations are replaced with struct instantiations - no matter where they are in the function body!

If you need to reuse the same variant within a function, just use the normal construction syntax:

#[errgo]
fn foo() -> Result<(), FooError> {
    fallible_op().map_err(|e| err!(IoError(io::Error = e)));
    Err(FooError::IoError(todo!()))
}