You can thank ASCIIFlow for that ;)
HN user
shepmaster
Cofounder of the world's first Rust consultancy[1] — we are available to help you and your company with any and everything related to Rust! We also produce the Rust in Motion video series[2] for Manning.
[1]: http://integer32.com/ [2]: https://www.manning.com/livevideo/rust-in-motion?a_aid=jgoulding&a_bid=6a993c2e
Haven't used --update-refs, but reading it, it should result in your third graph.
I don't think it does. I tried locally:
mkdir test; cd test; git init
touch a; git add a; git commit -m 'a'
touch b; git add b; git commit -m 'b'
touch c; git add c; git commit -m 'c'
git checkout -b branched-feature HEAD~
touch d; git add d; git commit -m 'd'
git checkout main
echo 'change' > b; git add b; git commit -m 'fix b'
git rebase -i --root --update-refs
And ended up with this graph (`git log --graph --all`) * commit (HEAD -> main)
|
| c
|
* commit
|
| b
|
| * commit (branched-feature)
| |
| | d
| |
| * commit
|/
| b
|
* commit
a
Replacing the `git commit -m 'fix b'; git rebase -i --root --update-refs` with `git history fixup HEAD~` produces what I'd like: * commit (branched-feature)
|
| d
|
| * commit (HEAD -> main)
|/
| c
|
* commit
|
| b
|
* commit
aI'm not sure that answers my question. That shows a linear set of branches (my-feature-v3 depends on my-feature-v2 depends on my-feature-v1 depends on main). I'm asking about the case where two or more branches fork from a common ancestor and you want to fix the common ancestor.
Ah, good catch. That's more of a graphic issue and not what I was trying to express. Updated the OP.
That last part goes further than git rebase --update-refs, which only moves refs sitting inside the range you’re actively rebasing. git history instead finds and rewrites every local branch descended from the commit (while also having an option to limit it to only the current branch).
I'm reading that to mean that when I use `git rebase --update-refs` in this situation, where I've currently checked out `D` and update `B` to `B'`:
A ──► B ──► C ──► D
│
└───► E
I'll end up with this state, where `E` remains untouched? A ──► B' ─► C' ─► D'
│
└───► B ──► E
(EDIT: Originally I had `E` point to `B'`, which doesn't make sense)If I use `git history fixup`, it would also update `E` and end up with this?
A ──► B' ─► C' ─► D'
│
└───► E'
If that's the case, is there a way to get `git rebase` to have the same behavior? I've got decades of `git rebase` burned into my fingers at this point.At a Pittsburgher, I assumed it was a misspelling of “nebby”.
But it's not natural to do so.
I tend to write most of my async Rust following the actor model and I find it natural. Alice Rhyl, a prominent Tokio contributor, has written about the specific patterns:
An official post about this is at
https://blog.rust-lang.org/2025/09/12/crates-io-phishing-cam...
You are quite welcome; thanks for the kind words!
You certainly can use thiserror to accomplish the same goals! However, your example does a little subtle slight-of-hand that you probably didn't mean to and leaves off the enum name (or the `use` statement):
low_level_result.context(ErrorWithContextSnafu { context })?;
low_level_result.map_err(|err| CustomError::ErrorWithContext { err, context })?;
Other small details:- You don't need to move the inner error yourself.
- You don't need to use a closure, which saves a few characters. This is even true in cases where you have a reference and want to store the owned value in the error:
#[derive(Debug, Snafu)]
struct DemoError { source: std::io::Error, filename: PathBuf }
let filename: &Path = todo!();
result.context(OpenFileSnafu { filename })?; // `context` will change `&Path` to `PathBuf`
- You can choose to capture certain values implicitly, such as a source file location, a backtrace, or your own custom data (the current time, a global-ish request ID, etc.)----
As an aside:
#[error("failed to open a: {0}")]
It is now discouraged to include the text of the inner error in the `Display` of the wrapping error. Including it leads to duplicated data when printing out chains of errors in a nicer / structured manner. SNAFU has a few types that work to undo this duplication, but it's better to avoid it in the first place.In addition to the sibling comment mentioning thiserror, I also submit my crate SNAFU (linked in my ancestor comment). Reducing some of the boilerplate is a big reason I enjoy using it.
I create one error type per function/action
I do too! I've been debating whether I should update SNAFU's philosophy page [1] to mention this explicitly, and I think your comment is the one that put me over the edge for "yes" (along with a few child comments). Right now, it simply says "error types that are scoped to that module", but I no longer think that's strong enough.
[1] https://docs.rs/snafu/latest/snafu/guide/philosophy/index.ht...
Thanks for using SNAFU! Any feedback you'd like to share?
As we all know, Cool URIs don't change [1]. I greatly appreciate the care taken to keep these Compiler Explorer links working as long as possible.
The Rust playground uses GitHub Gists as the primary storage location for shared data. I'm dreading the day that I need to migrate everything away from there to something self-maintained.
I hope to read through your crate and examples later, but if you have a chance, I’d be curious to hear your take on how Stack Error differs from my library, SNAFU [1]!
SNAFU author here, thanks for including my crate! I’ll try to give your review a thorough read through later and incorporate feedback that makes sense.
I do have https://diataxis.fr/ and related stuff open in another tab and keep meaning to figure out how to best apply it for SNAFU.
Out of curiosity, do you recall if you also read the top-level docs[1]? That’s intended to be the main introduction, I actually don’t expect most people to read the user’s guide, unfortunately.
I agree. Unfortunately, I think that a lot of the people who ask for a bigger standard library really just want (a) someone else to do the work (b) someone they can trust.
The people working on Rust are a finite (probably overextended!) set of people and you can't just add more work to their plate. "Just" making the standard library bigger is probably a non-starter.
I think it'd be great if some group of people took up the very hard work to curate a set of crates that everyone would use and provide a nice façade to them, completely outside of the Rust team umbrella. Then people can start using this Katamari crate to prove out the usefulness of it.
However, many people wouldn't use it. I wouldn't because I simply don't care and am happy adding my dependencies one-by-one with minimal feature sets. Others wouldn't because it doesn't have the mystical blessing/seal-of-approval of the Rust team.
I agree with your general point, but for this specific functionality, I’ll point out that setting environment variables of the current process is unsafe. It took us a long time to realize it so the function wasn’t actually marked as unsafe until the Rust 2024 edition.
What this means in practice is that the call to invoke dotenv should also be marked as unsafe so that the invoker can ensure safety by placing it at the right place.
If no one is maintaining the crate, that won’t happen and someone might try to load environment variables at a bad time.
Yeah, with SNAFU I try to encourage people going all-in on very fine-grained error types. I love it (unsurprisingly).
[Rust and C++] encourages creating a library of types with inheritance
You'll want to expand on and clarify your meaning here, as Rust does not have inheritance.
Yep, as the sibling said, it’s to prove the point in a snarky manner. The yellow-on-white color, the wobbling, upside down path of text, the font choice, the unrelated image; all of these serve to make the concept actively hard to process.
Hopefully this sparks a little “wow I wonder if my image of text was also hard to read like I just experienced” moment.
We made this to be used as a reply when pictures are misused where text would be better:
Agree with simonw’s sibling comment. To add to it, I’m the primary maintainer of the Rust playground and basically self-review every single commit.
The rust-lang/rust repository has higher scrutiny (in part driven by tools like bors, the subject of the article).
That is a use of https://en.wikipedia.org/wiki/Weathering_steel, actually!
Discussed three days ago:
This is one of the things I like about SNAFU: it makes this preferred pattern the default and makes it nicer to use. For example, your usage would look something like this with SNAFU:
create_dir(path).context(CreateStagingDirectorySnafu { path })?;
Note a few points:1. No need to use the closure
2. No need to carry the source error over yourself (`context` does this for you)
3. No need to explicitly call `clone` on the path (`context` does this for you)
When presented with a bug from the field, I also care about finding the path through my code where it occurred, but rarely do I need to know that `foo` called `foo_with_caching` called `foo_with_caching_recursive`. When reading a backtrace, I skip over amounts of "implementation details" to get a big picture. For me, the exact functions / files / line numbers are not relevant, doubly so if I'm working in a situation where the error message isn't tied to a specific git commit and the functions/files/lines have moved over time.
To reiterate my point from above though, my error stacks are all unique — seeing the stack will point me to an exact line in my code where the error occurred, even though I don't include function/file/line as-is.
I don't think you need a lint. When you define the error type returned by `handle_request`, you decide how the error type returned by `handle_request` will be incorporated. If you've decided to implement `From` then you've decided you don't want/need to add context. Otherwise, the compiler will give you an error when you use `?`.
The time I can think this won't work is when you are reusing error types across places. Recently, I've been experimenting with creating a lot of error types, so far as one unique error type per function. I haven't done this for long enough to have a real report, but I haven't hated it so far.
Without deeply looking into it, I'd expect that to integrate with SNAFU, you could basically write something like this:
struct SpanTraceWrapper(tracing_error::SpanTrace);
impl snafu::GenerateImplicitData for SpanTraceWrapper {
fn generate() -> Self {
Self(tracing_error::SpanTrace::capture())
}
}
And then you can use it as #[derive(Debug, Snafu)]
struct SomeError {
#[snafu(implicit)]
span_trace: SpanTraceWrapper,
}
This will capture the `SpanTrace` whenever `SomeError` is constructed (e.g. `thing().context(SomeSnafu)` or `SomeSnafu.fail()`.SNAFU follows much the same idea: we have an attribute you can add [0] when you want to allow directly implementing `From`. Like thiserror, you can also mark an error as transparent [1] when even the error existing doesn't provide useful information.
[0] https://docs.rs/snafu/latest/snafu/derive.Snafu.html#disabli...
[1] https://docs.rs/snafu/latest/snafu/derive.Snafu.html#delegat...