Expand description
§Module :: error_tools
A unified error handling facade that provides a consistent interface for both typed and untyped error handling in Rust. error_tools
acts as a standardized wrapper around the popular thiserror
and anyhow
crates, enabling you to write error-handling code once and use it consistently across different contexts.
§Why error_tools?
When building Rust applications and libraries, you often face these error handling challenges:
- Library vs Application Choice: Libraries typically use
thiserror
for typed errors, while applications preferanyhow
for flexibility - Inconsistent Error Patterns: Different crates in your dependency tree use different error handling approaches
- Dependency Fragmentation: Having both
anyhow
andthiserror
as direct dependencies across multiple crates - Context Switching: Different syntax and patterns for similar error handling tasks
- Integration Friction: Converting between different error types when bridging library and application code
error_tools solves these problems by providing:
- 🎯 Unified Interface: Single import pattern for both typed and untyped errors
- 📦 Dependency Facade: Centralized re-export of
anyhow
andthiserror
functionality - 🔧 Enhanced Utilities: Additional error handling utilities like
ErrWith
trait - 🏗️ Consistent Patterns: Standardized error handling across the entire wTools ecosystem
- 🚀 Easy Migration: Drop-in replacement for existing
anyhow
/thiserror
usage - 🛡️ no_std Support: Works in
no_std
environments when needed
§Quick Start
§Installation
cargo add error_tools
§Basic Usage
Choose your approach based on your needs:
// For applications - flexible, untyped errors (anyhow-style)
use error_tools::untyped::*;
// For libraries - structured, typed errors (thiserror-style)
use error_tools::typed::*;
use error_tools::dependency::thiserror;
// For convenience - includes both
use error_tools::prelude::*;
§Core Concepts
§1. Untyped Errors (Application-Focused)
Perfect for applications where you need flexible error handling without defining custom error types for every possible failure. This is a direct facade over anyhow
.
Key Features:
- Dynamic error handling with context
- Easy error chaining and reporting
- Rich context information
- Perfect for rapid prototyping and applications
use error_tools::untyped::{ Result, format_err };
fn get_message() -> Result< &'static str >
{
Ok( "Hello, world!" )
// Err( format_err!( "An unexpected error!" ) )
}
fn main()
{
match get_message()
{
Ok( msg ) => println!( "Success: {}", msg ),
Err( e ) => println!( "Error: {:?}", e ),
}
}
Run this example:
cargo run --example error_tools_trivial
§2. Working with Context
Adding context to errors helps with debugging and user experience:
use error_tools::untyped::{ Result, Context, format_err };
fn read_and_process_file( path : &str ) -> Result< String >
{
// Simulate file reading for demonstration
let content = if path == "test.txt" { "hello world" } else { "" };
if content.is_empty()
{
return Err( format_err!( "File is empty or not found: {}", path ) );
}
Ok( content.to_uppercase() )
}
fn main()
{
match read_and_process_file( "test.txt" )
{
Ok( content ) => println!( "Processed: {}", content ),
Err( e ) => println!( "Error: {}", e ),
}
}
See the full runnable example in
examples/replace_anyhow.rs
.
§3. Typed Errors (Library-Focused)
Ideal for libraries where you want to provide a clear, structured contract for possible errors. This is a facade over thiserror
.
Key Features:
- Structured error types with derive macros
- Clear error hierarchies
- Compile-time error checking
- Better API boundaries for library consumers
use error_tools::typed::Error;
use error_tools::dependency::thiserror;
#[ derive( Debug, Error ) ]
pub enum DataError
{
#[ error( "I/O error for file: {file}" ) ]
Io { file : String },
#[ error( "Parsing error: {0}" ) ]
Parse( String ),
}
fn process_data( file_name : &str, content : &str ) -> Result< i32, DataError >
{
if content.is_empty()
{
return Err( DataError::Io { file : file_name.to_string() } );
}
content.trim().parse::< i32 >()
.map_err( | _ | DataError::Parse( "Could not parse content as integer".into() ) )
}
fn main()
{
match process_data( "data.txt", "123" )
{
Ok( num ) => println!( "Parsed number: {}", num ),
Err( e ) => println!( "Error: {}", e ),
}
// Example with error
match process_data( "invalid.txt", "abc" )
{
Ok( _ ) => (),
Err( e ) => println!( "Expected error: {}", e ),
}
}
See the full runnable example in
examples/replace_thiserror.rs
.
§4. Enhanced Error Context with ErrWith
The ErrWith
trait provides additional utilities for adding context to errors:
use error_tools::{ ErrWith };
fn process_user_data( user_id : u32, data : &str ) -> Result< String, ( String, Box< dyn std::error::Error > ) >
{
// Add context using closures for lazy evaluation
let parsed_data = data.parse::< i32 >()
.err_with( || format!( "Failed to parse data for user {}", user_id ) )?;
// Add context using references for simple messages
let processed = perform_calculation( parsed_data )
.err_with_report( &format!( "Calculation failed for user {}", user_id ) )?;
Ok( format!( "Processed: {}", processed ) )
}
fn perform_calculation( input : i32 ) -> std::result::Result< i32, &'static str >
{
if input < 0
{
Err( "Negative numbers not supported" )
}
else
{
Ok( input * 2 )
}
}
fn main()
{
match process_user_data( 123, "42" )
{
Ok( result ) => println!( "Success: {}", result ),
Err( ( report, err ) ) => println!( "Error: {} - {:?}", report, err ),
}
}
See the full runnable example in
examples/err_with_example.rs
.
§5. Debug Assertions
Additional debugging utilities for development:
use error_tools::{ debug_assert_id, debug_assert_ni };
fn validate_data( expected : &str, actual : &str )
{
// Only active in debug builds
debug_assert_id!( expected, actual, "Data validation failed" );
// Negative assertion
debug_assert_ni!( expected, "", "Expected data should not be empty" );
}
fn main()
{
validate_data( "test", "test" );
println!( "Debug assertions passed!" );
}
§Examples
§Basic Error Handling
use error_tools::untyped::Result;
fn might_fail( should_fail : bool ) -> Result< String >
{
if should_fail
{
Err( error_tools::untyped::format_err!( "Something went wrong" ) )
}
else
{
Ok( "Success!".to_string() )
}
}
fn main()
{
match might_fail( false )
{
Ok( msg ) => println!( "Result: {}", msg ),
Err( e ) => println!( "Error: {}", e ),
}
}
§Using Both Typed and Untyped Errors
use error_tools::prelude::*;
use error_tools::dependency::thiserror;
// Typed error for library API
#[ derive( Debug, Error ) ]
pub enum ConfigError
{
#[ error( "Configuration file not found" ) ]
NotFound,
#[ error( "Invalid format: {0}" ) ]
InvalidFormat( String ),
}
// Function returning typed error
fn load_config_typed() -> Result< String, ConfigError >
{
Err( ConfigError::NotFound )
}
// Function returning untyped error
fn load_config_untyped() -> error_tools::untyped::Result< String >
{
Err( error_tools::untyped::format_err!( "Configuration loading failed" ) )
}
fn main()
{
// Handle typed error
if let Err( e ) = load_config_typed()
{
println!( "Typed error: {}", e );
}
// Handle untyped error
if let Err( e ) = load_config_untyped()
{
println!( "Untyped error: {}", e );
}
}
§Feature Flags
error_tools
supports granular feature control:
[dependencies]
error_tools = { version = "0.26", features = [ "error_typed" ] } # Only typed errors
# or
error_tools = { version = "0.26", features = [ "error_untyped" ] } # Only untyped errors
# or
error_tools = { version = "0.26" } # Both (default)
Available Features:
default
- Enables botherror_typed
anderror_untyped
error_typed
- Enablesthiserror
integration for structured errorserror_untyped
- Enablesanyhow
integration for flexible errorsno_std
- Enablesno_std
supportuse_alloc
- Enables allocation support inno_std
environments
§Migration Guide
§From anyhow
Replace your anyhow
imports with error_tools::untyped
:
// Before
// use anyhow::{ Result, Context, bail, format_err };
// After
use error_tools::untyped::{ Result, Context, bail, format_err };
fn main()
{
println!("Migration complete - same API, different import!");
}
Everything else stays the same!
§From thiserror
Add the explicit thiserror
import and use error_tools::typed
:
// Before
// use thiserror::Error;
// After
use error_tools::typed::Error;
use error_tools::dependency::thiserror; // Required for derive macros
fn main()
{
println!("Migration complete - same derive macros, unified import!");
}
The derive macros work identically.
§Complete Examples
Explore these runnable examples in the repository:
# Basic usage patterns
cargo run --example error_tools_trivial
# Migration from anyhow
cargo run --example replace_anyhow
# Migration from thiserror
cargo run --example replace_thiserror
# Using the ErrWith trait
cargo run --example err_with_example
§Best Practices
§1. Choose the Right Error Style
- Applications: Use
untyped
errors for flexibility and rapid development - Libraries: Use
typed
errors for clear API contracts and better user experience - Mixed Projects: Use both as appropriate - they interoperate well
§2. Error Context
Always provide meaningful context:
use error_tools::untyped::{ Result, Context, format_err };
fn process_user_data( user_id : u32 ) -> Result< String >
{
// Good - specific context
let _result = simulate_operation()
.context( format!( "Failed to process user {} data", user_id ) )?;
// Less helpful - generic context
let _other = simulate_operation()
.context( "An error occurred" )?;
Ok( "Success".to_string() )
}
fn simulate_operation() -> Result< String >
{
Ok( "data".to_string() )
}
fn main()
{
match process_user_data( 123 )
{
Ok( result ) => println!( "Result: {}", result ),
Err( e ) => println!( "Error: {}", e ),
}
}
§3. Error Hierarchies
For libraries, design clear error hierarchies:
use error_tools::typed::Error;
use error_tools::dependency::thiserror;
#[ derive( Debug, Error ) ]
pub enum LibraryError
{
#[ error( "Configuration error: {0}" ) ]
Config( #[from] ConfigError ),
#[ error( "Network error: {0}" ) ]
Network( #[from] NetworkError ),
#[ error( "Database error: {0}" ) ]
Database( #[from] DatabaseError ),
}
// Define the individual error types
#[ derive( Debug, Error ) ]
pub enum ConfigError
{
#[ error( "Config not found" ) ]
NotFound,
}
#[ derive( Debug, Error ) ]
pub enum NetworkError
{
#[ error( "Connection failed" ) ]
ConnectionFailed,
}
#[ derive( Debug, Error ) ]
pub enum DatabaseError
{
#[ error( "Query failed" ) ]
QueryFailed,
}
fn main()
{
let config_err = LibraryError::Config( ConfigError::NotFound );
println!( "Error hierarchy example: {}", config_err );
}
§4. Dependency Access
When you need direct access to the underlying crates:
// Access the underlying crates if needed
// use error_tools::dependency::{ anyhow, thiserror };
// Or via the specific modules
use error_tools::untyped; // Re-exports anyhow
use error_tools::typed; // Re-exports thiserror
fn main()
{
println!("Direct access to underlying crates available via dependency module");
}
§Integration with wTools Ecosystem
error_tools
is designed to work seamlessly with other wTools crates:
- Consistent Error Handling: All wTools crates use
error_tools
for unified error patterns - Cross-Crate Compatibility: Errors from different wTools crates integrate naturally
- Standardized Debugging: Common debugging utilities across the ecosystem
§To add to your project
cargo add error_tools
§Try out from the repository
git clone https://github.com/Wandalen/wTools
cd wTools
cargo run --example error_tools_trivial
# Or try the specific examples
cargo run --example replace_anyhow
cargo run --example replace_thiserror
cargo run --example err_with_example
Re-exports§
pub use assert::*;
Modules§
- assert
- Assertions.
- dependency
- Namespace with dependencies.
- error
- Core error handling utilities. Core error handling utilities.
- prelude
- Prelude to use essentials:
use error_tools::prelude::*
. - typed
- Typed error handling, a facade for
thiserror
. Typed error handling, a facade forthiserror
. - untyped
- Untyped error handling, a facade for
anyhow
. Untyped error handling, a facade foranyhow
.
Macros§
- anyhow
- Construct an ad-hoc error from a string or existing non-
anyhow
error value. - bail
- Return early with an error.
- debug_
assert_ id - Macro asserts that two expressions are identical to each other. Unlike
std ::assert_eq
it is removed from a release build. - debug_
assert_ identical - Macro asserts that two expressions are identical to each other. Unlike
std ::assert_eq
it is removed from a release build. Alias ofdebug_assert_id
. - debug_
assert_ ni - Macro asserts that two expressions are not identical to each other. Unlike
std ::assert_eq
it is removed from a release build. - debug_
assert_ not_ identical - Macro asserts that two expressions are not identical to each other. Unlike
std ::assert_eq
it is removed from a release build. - ensure
- Return early with an error if a condition is not satisfied.
- format_
err - Construct an ad-hoc error from a string or existing non-
anyhow
error value.
Structs§
- Error
- The
Error
type, a wrapper around a dynamic error type.
Traits§
- Context
- Provides the
context
method forResult
. - ErrWith
- Trait to add extra context or information to an error.
- Error
Trait Error
is a trait representing the basic expectations for error values, i.e., values of typeE
inResult<T, E>
.
Functions§
- Ok
- Equivalent to
Ok::<_, anyhow::Error>(value)
.
Type Aliases§
- Result
Result<T, Error>
- Result
With Report - A type alias for a
Result
that contains an error which is a tuple of a report and an original error.