| Crates.io | error_tools |
| lib.rs | error_tools |
| version | 0.34.0 |
| created_at | 2022-06-12 08:05:52.429108+00 |
| updated_at | 2025-09-23 09:56:25.530019+00 |
| description | Basic exceptions handling mechanism |
| homepage | https://github.com/Wandalen/wTools/tree/master/module/core/error_tools |
| repository | https://github.com/Wandalen/wTools/tree/master/module/core/error_tools |
| max_upload_size | |
| id | 604477 |
| size | 205,747 |
error_toolsA 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.
When building Rust applications and libraries, you often face these error handling challenges:
thiserror for typed errors, while applications prefer anyhow for flexibilityanyhow and thiserror as direct dependencies across multiple crateserror_tools solves these problems by providing:
anyhow and thiserror functionalityErrWith traitanyhow/thiserror usageno_std environments when neededcargo add error_tools
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::*;
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:
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
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.
Ideal for libraries where you want to provide a clear, structured contract for possible errors. This is a facade over thiserror.
Key Features:
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.
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.
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!" );
}
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 ),
}
}
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 );
}
}
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 both error_typed and error_untypederror_typed - Enables thiserror integration for structured errorserror_untyped - Enables anyhow integration for flexible errorsno_std - Enables no_std supportuse_alloc - Enables allocation support in no_std environmentsReplace 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!
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.
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
untyped errors for flexibility and rapid developmenttyped errors for clear API contracts and better user experienceAlways 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 ),
}
}
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 );
}
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");
}
error_tools is designed to work seamlessly with other wTools crates:
error_tools for unified error patternscargo add error_tools
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