| Crates.io | errgonomic |
| lib.rs | errgonomic |
| version | 0.4.1 |
| created_at | 2025-12-29 06:43:43.539793+00 |
| updated_at | 2026-01-24 12:45:33.418372+00 |
| description | Macros for ergonomic error handling with thiserror |
| homepage | https://github.com/DenisGorbachev/errgonomic |
| repository | https://github.com/DenisGorbachev/errgonomic |
| max_upload_size | |
| id | 2010087 |
| size | 140,408 |
Macros for ergonomic error handling with thiserror.
#[derive(Serialize, Deserialize)]
struct Config {/* some fields */}
// bad: doesn't return the path to config (the user won't be able to fix it)
fn parse_config_v1(path: PathBuf) -> io::Result<Config> {
let contents = read_to_string(&path)?;
let config = from_str(&contents).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
Ok(config)
}
// good: returns the path to config & the underlying deserialization error (the user will be able fix it)
fn parse_config_v2(path: PathBuf) -> Result<Config, ParseConfigError> {
use ParseConfigError::*;
let contents = handle!(read_to_string(&path), ReadToStringFailed, path);
let config = handle!(from_str(&contents), DeserializeFailed, path, contents);
Ok(config)
}
#[derive(Error, Debug)]
enum ParseConfigError {
#[error("failed to read file to string: '{path}'")]
ReadToStringFailed { path: PathBuf, source: std::io::Error },
#[error("failed to parse the file contents into config: '{path}'")]
DeserializeFailed { path: PathBuf, contents: String, source: serde_json::Error }
}
Advantages:
parse_config_v2 allows you to determine exactly what error has occurredparse_config_v2 provides you with all information needed to fix the underlying issueparse_config_v2 allows you to retry the call by reusing the path (avoiding unnecessary clones)Disadvantages:
parse_config_v2 is longerThat means parse_config_v2 is strictly better but requires writing more code. However, with LLMs, writing more code is not an issue. Therefore, it's better to use a more verbose approach v2, which provides you with better errors.
This crates provides the handle family of macros to simplify the error handling code.
To improve your debugging experience: call exit_result in main right before return, and it will display all information necessary to understand the root cause of the error:
pub fn main() -> ExitCode {
exit_result(run())
}
This will produce a nice "error trace" like below:
- failed to run CLI command
- failed to run i18n update command
- failed to update 2 rows
- encountered 2 errors
* - failed to send an i18n request for row 'Foo'
- failed to construct a JSON schema
- input must be a JSON object
* - failed to send an i18n request for row 'Bar'
- failed to send a request
- server at 239.143.73.1 did not respond
Goal: Help the caller diagnose the issue, fix it, and retry the call.
Approach: Every error must be represented by a unique enum variant with relevant fields.
Ok or an Errdata types, not for non-data typessecrecy::SecretBox)impl Iterator<Item = Result<T, E>> instead of short-circuiting on the first errorresult_large_err warning, then the large fields of the error enum must be wrapped in a Boxsource field, then this field must be the first fieldunwrap or expect (only tests may use unwrap or expect)Copy, then the error enum must implement Copy tooCopy, the callee must not include it in the list of error enum variant fields (the caller must include it because of the rule to include all relevant owned variables)use ThisFunctionError::*;, where ThisFunctionError must be the name of this function's error enum (for example: use ParseConfigError::*;)ReadFileFailed instead of ParseConfigError::ReadFileFailed)Error (for example: ParseConfigError)Failed or NotFound or Invalid (for example: ReadFileFailed, UserNotFound, PasswordInvalid)Failed (for example: if the parent function calls read_file, then it should call it like this: handle!(read_file(&path), ReadFileFailed, path)ErrorErrorErrorTryFrom<A> for B impl, then its name must be equal to "Convert{A}To{B}Error"Use the following macros for more concise error handling:
handle! instead of Result::map_errhandle_opt! instead of Option::ok_or and Option::ok_or_elsehandle_bool! instead of if condition { return Err(...) }handle_iter! instead of code that handles errors in iteratorshandle_iter_of_refs! instead of code that handles errors in iterators of references (where the values are still being owned by the underlying collection)handle_into_iter! instead of code that handles errors in collections that implement IntoIterator (including Vec and HashMapAn expression that returns a Result.
A type that holds the actual data.
For example:
boolStringPathBufA type that doesn't hold the actual data.
For example:
RestClient doesn't point to the actual data, it only allows querying it.DatabaseConnection doesn't hold the actual data, it only allows querying it.cargo add errgonomic
Like the project? ⭐ Star this repo on GitHub!
Apache-2.0 or MIT.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, shall be licensed as above, without any additional terms or conditions.