-
-
Notifications
You must be signed in to change notification settings - Fork 415
Description
Tasks
- Proof of concept
-
anyhowinteraction (to keep the source-chain alive)- make
cargo nextest --workflowrun without--exclude gix-errorby having multiple expectations there, depending on the set feature toggles.
- make
- replace
thiserrorwithgix-erroreverywhere - Make it easy to differentiate between
NotARepositoryand something went wrong opening it
Related Issues
PRs
gix-errorpunch-through #2352- Convert more crates to
gix-error#2373 gix-commitgraphtogix-error#2378anyhowintegration forgix-error#2383- feat!: replace
thiserrorwith a custom implemenation of error types. #2389 - Make validate errors non-exhaustive. #2390
- More
gix-errorconversions: gix-actor #2396 - More of
gix-error#2400
thiserror usages
138 gix
23 gix-pack
22 gix-ref
18 gix-filter
12 gix-object
11 gix-odb
11 gix-index
11 gix-config
10 gix-transport
9 gix-protocol
8 gix-merge
8 gix-diff
7 gix-hash
6 gix-submodule
6 gix-credentials
4 gix-revwalk
4 gix-discover
3 gix-url
3 gix-traverse
3 gix-pathspec
3 gix-packetline
3 gix-features
2 gix-status
2 gix-shallow
2 gix-path
2 gix-config-value
2 gix-attributes
1 gix-worktree-stream
1 gix-worktree-state
1 gix-refspec
1 gix-quote
1 gix-prompt
1 gix-mailmap
1 gix-lock
1 gix-fs
1 gix-dir
1 gix-blame
1 gix-bitmap
1 gix-archive
GenAI Notes
Refine this prompt for better results, going one crate at a time.
In the
CRATENAME, replacethiserrorwithgix-errorafter reading the documentation ofgix-error/src/lib.rscarefully to know how to usegix-errorcorrectly.
Actually, genAI isn't good at this, it just doesn't get it and creates a convoluted mess.
What is can do is turn thiserror into the manual implementation, but that's not super useful to start with, and I'd argue that one can do this better by hand.
Fair enough, it's my daily night task.
Benefits of the exn crate compared to thiserror/anyhow
The benefits of exn:
- it's small at ~300 SLOC (
anyhowhas 14k) - it doesn't use proc-macros and has 0 dependencies (
thiserrorhas 3 or 4 heavy ones) it doesn't leak out of the crate, error types are hand-implemented structs or enums- Actually it does leak out, as the examples show an
exn::Resultwhich hides theResult<(), Exn<ErrorType>>
- Actually it does leak out, as the examples show an
- Call locations by default, without overhead or full backtraces
Disadvantages compared to thiserror
- The
Exntype is exposed in the typesystem, it wraps the actual type.- This can be hidden with
exn::Result, and is very common also foranyhow::Resultin applications. It's not common in plumbing crates, but I feel strongly that hiding it will be enough, with benefits clearly outweighing the disadvantage of marryinggix-withexnin that way. - If that's ever a problem, it can be moved into
gix-errorseven, and maybe that is what should be done to gain a little distance.
- This can be hidden with
Benefits of the exn error handling style
- sources of errors are gathered automatically
- error chains/trees are searchable by downcasting
- errors are organised by their value for the caller, and not by what went wrong
Of course, the presentation of errors, can be adjusted, but this is completely controlled by the calling application, and gix could provide its own application errors as utility if it wanted to (probably not).
Basic Example
// Copyright 2025 FastLabs Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! # Basic Example - Error Handling Best Practices
//!
//! This example demonstrates the recommended patterns for using `exn`:
//!
//! 1. **Define Error Types Per Module** - Each module has its own error type. The type system
//! enforces proper error context via `or_raise()`.
//!
//! 2. **Don't Chain Errors Manually** - Unlike traditional error handling, you don't need `source:
//! Box<dyn Error>` in your types. The `exn` framework maintains the error chain automatically.
//!
//! 3. **Keep Errors Simple** - Use `struct Error(String)` by default. Only add complexity (enums,
//! fields) when needed for programmatic handling.
use derive_more::Display;
use exn::Result;
use exn::ResultExt;
use exn::bail;
fn main() -> Result<(), MainError> {
app::run().or_raise(|| MainError)?;
Ok(())
}
#[derive(Debug, Display)]
#[display("fatal error occurred in application")]
struct MainError;
impl std::error::Error for MainError {}
mod app {
use super::*;
pub fn run() -> Result<(), AppError> {
// When crossing module boundaries, use or_raise() to add context
http::send_request("https://example.com")
.or_raise(|| AppError("failed to run app".to_string()))?;
Ok(())
}
#[derive(Debug, Display)]
pub struct AppError(String);
impl std::error::Error for AppError {}
}
mod http {
use super::*;
pub fn send_request(url: &str) -> Result<(), HttpError> {
std::fs::File::open("does not exist")
.or_raise(|| HttpError(format!("Failed to open {url}")))
.map(|_| ())
}
#[derive(Debug, Display)]
pub struct HttpError(String);
impl std::error::Error for HttpError {}
}
// Error: fatal error occurred in application, at examples/src/basic.rs:34:16
// |
// |-> failed to run app, at examples/src/basic.rs:49:14
// |
// |-> Failed to open https://example.com, at examples/src/basic.rs:63:14
// |
// |-> No such file or directory (os error 2), at examples/src/basic.rs:63:1Needed in exn
Things I noticed when porting
- Good debug printing so we get something akin to
failed to create index: Git(FetchDuringClone(PrepareFetch(RefMap(Handshake(Transport(Io(Custom { kind: Other, error: "error sending request for url (https://github.com/rust-lang/crates.io-index/info/refs?service=git-upload-pack)" })))))))- Be sure this contains call locations, allowing people to help themselves more easily.
- Validate that interop with
anyhow, so that error chains work correctly.- Actually,
gix-errorwould have to have a feature (default on viagix) to auto-setup an error chain and completely dissolveExn. - Chained parts should always be linked-lists, until they can't be, or the 'chain' feature is set.
- Actually, let's do it with an
into-anyhowfeature that uses publicly accessible methods to convert into an anyhow::Error.
- Actually,
- Error iterator similar to
iter_chain()orsources(). Note thatsource()has been converted intoSourceError Errormust not loose call locations, try to keep them by using frames.