Expand description
The primary macro in this crate is errors, designed to simplify error handling by allowing developers to define and restrict error types directly within functions. This reduces boilerplate code and improves readability.
§Usage
§Basic Example
Below is a basic example of how to use the errors macro:
#[errors(std::io::Error)]
fn foo() -> Result<i32, _> {
std::fs::File::open("hello.txt")?;
Ok(10)
}
fn bar() {
match foo() {
Err(FooError::StdIo(_)) => {/* Handle error */},
Ok(_) => {/* Handle success */}
}
}This macro automatically generates an enum resembling the following and sets the Result error type to it, so developers do not need to manually define it:
// Auto generated code from `#[errors]`
#[derive(Debug)]
enum FooError {
StdIo(std::io::Error),
}
impl From<std::io::Error> for FooError { ... }
impl Display for FooError { ... }
impl Error for FooError {}Defining no errors also works, which will generate an enum with no variants, enforcing that no errors are returned. This is useful for functions that are guaranteed not to fail but still require a Result<...> return type, such as in trait implementations. It provides extra safety by ensuring that no error paths are possible.
§Enum name
You can also explicitly set the enum name by using a Ident instead of _ in the signature
#[errors(std::io::Error)]
fn foo() -> Result<i32, MyErrorEnum> {
std::fs::File::open("hello.txt")?;
Ok(10)
}
fn bar() {
match foo() {
Err(MyErrorEnum::StdIo(_)) => {/* ... */},
Ok(_) => {/* ... */}
}
}§Usage in impl Blocks
To use the macro within an impl block, the block must also be annotated:
#[errors]
impl MyStruct {
#[errors(std::io::Error)]
fn method(&self) -> Result<(), _> {
Ok(())
}
}§Usage with anyhow::Result
The macro can also be used without overwriting an error type and is fully compatible with anyhow::Result and similar types. This is especially useful for developers who prefer using anyhow for general error handling but want to benefit from additional error type restrictions when needed, particularly in trait implementations:
#[errors]
fn foo() -> anyhow::Result<()> {
// ❌ Compiler error: `std::fs::File::open(...)` returns `std::io::Error`,
// but `#[errors]` enforces that no errors are allowed.
std::fs::File::open("hello.txt")?;
Ok(())
}§Upcasting types
#[errors(Err1, Err2)]
fn foo() -> Result<i32, _> {
// ...
}
#[errors(Err1, Err2, Err3)]
fn bar() -> Result<i32, _> {
let result = foo().into_super_error::<BarError>()?;
Ok(result)
}§Deriving traits for generated enum
You can annotate the function with #[derive] to derive traits for the generated enum.
Note that the #[derive] macro must be used after the errors macro. (technically in impl
blocks the order doesnt matter, but we recommend using #[derive] after errors for consistency.)
#[errors(Err1)]
#[derive(Clone)]
fn foo() -> Result<(), _> {
Ok(())
}
fn bar() {
let _ = foo().clone();
}§Specifics and Implementation Details
§Error Type Overwriting
The macro looks for a type named Result in the root of the return type. If the second generic argument is _, it replaces it with the appropriate error type. See the examples below:
| Original | Modified |
|---|---|
Result<T, _> | Result<T, FooError> |
Result<T, CustomName> | Result<T, CustomName> |
Result<T, Box<dyn Error>> | Result<T, Box<dyn Error>> |
std::result::Result<T, _> | std::result::Result<T, FooError> |
anyhow::Result<T> | anyhow::Result<T> |
Vec<Result<T, _>> | ❌ compiler error, nested types arent replaced |
§Enum Visibility
The enum can either be emitted inside the function body, or alongside the function (in which case it takes on the functions visibility) based on the following conditions:
- If the second generic argument in
Resultis_its emitted outside the function. - If the second generic argument in
Resultis a simple identifier its emitted outside. - Otherwise, the enum is generated inside the function body and cannot be accessed externally.
(in the following example the enums are include to illustrate where the macro would generate them.)
#[errors]
fn foo() -> Result<(), _> { ... }
// enum FooError {...}
#[errors]
pub fn bar() -> Result<(), _> { ... }
// pub enum FooError {...}
#[errors]
pub fn baz() -> Result<(), CustomName> { ... }
// pub enum CustomName {...}
#[errors]
pub fn secret() -> anyhow::Result<()> {
// enum SecretError { ... }
// ...
}§Naming Conventions
The enum name is derived from the function name, converted to Pascal case using the case_fold crate to conform to Rust naming conventions for types and enums. Similarly, variant names are derived from the path segments of the types, with the “Error” suffix removed if present. For example, std::io::Error would produce a variant called StdIo, while io::Error would produce Io.
§Display Implementation
The Display implementation simply delegates to each contained error, ensuring consistent and readable error messages.
§into_super_error
This function uses the FlattenInto trait which is automatically implemented by the macro for
its errors, for all target types which implemnt From<...> for each of the errors variants. i.e a generated
implementation might look like:
impl<T> FlattenInto<T> for OurError
where T: From<Err1> + From<Err2> {
fn flatten(self) -> T {
match self {
Self::Err1(err) => T::from(err),
Self::Err2(err) => T::from(err),
}
}
}Modules§
Traits§
- Flatten
Into - This trait allows a error to be flattened into another one and is automatically implemented by
the
#[errors]macro for all super errors that implementFrom<...>for each of its fields. - Result
Ext - This trait extends
Resultwith an additional method to upcast a error enum.