Andrew Gallant (BurntSushi) writes about Rust, regex engines, and ripgrep, his fast command line search tool.
HN links it for the ripgrep benchmarks and his argument that using unwrap() in Rust is acceptable, which commenters cite in error-handling debates.
> I wrote about how to do error handling without libraries literally the day Rust 1.0 was published: https://burntsushi.net/rust-error-handling/
>
> That blog did include a recommendation for `failure` at one point, and now `anyhow`, but it's more of a footnote. The blog shows how to do error handling without any dependencies at all. You didn't have to jump on the error library treadmill. (Although I will say that `anyhow` and `thiserror` have been around for a number of years now and shows no signs of going away.)
Thank you -- I just wanna say, I read a lot of your writing and I love your work. I'm not sure if I read that blog post so many years ago but it looks like a good overview that has aged well.
> I happily downcast in ripgrep: https://github.com/BurntSushi/ripgrep/blob/0a88cccd5188074de...
>
> That also shows the utility of an error chain.
Yeah, I mean, that looks pretty nice.
I still think the error chain abstraction should actually be a tree.
And I think they should never have stabilized an `std::error::Error` trait that was not in core. I think that itself was a mistake. And 8 years later we're only now maybe able to get there.
I actually said something on a github issue about this before rust 1.0 stabilization, and that it would cause an ecosystem split with embedded, and that this really should be fixed, but my comment was not well received, and obviously didn't have much impact. I'll see if I can find it, it's on github and I remember withoutboats…
The ? operator has implicit error conversion. As another user said, it usses the From trait which performs conversions between one type and another. (Note that generally in Rust type conversions don't happen implicitly and you need to call .into() -- the ? situation is an exception)
If you want to have more finer grained control on the error conversion (the From conversion is always the same for some pair of error types) you can use map_err before calling ?, like something.map_err(|e| othererror)?
https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err
If you are using anyhow and wants to attach a string message to the error (maybe to explain what happened to the user), you can also use .context(), also called .wrap_err() in eyre (eyre is a fork of anyhow)
https://docs.rs/anyhow/latest/anyhow/trait.Context.html
https://docs.rs/eyre/latest/eyre/trait.WrapErr.html
The following article is ten years old, but was continually updated to keep it fresh and I think it does a good job explaining how error handling works in Rust:
https://burntsushi.net/rust-error-handling/
Depends on where you draw the line of what a parser is:
- If the parser is "the thing that comes after the lexer" then all of this is abstracted away by the lexer and you can just treat it as a span of bytes;
- If the parser is "everything that needs to be implemented to correctly transduce the input sequence into a tree", then you need to implement this yourself or have a lexer that handles this for you, usually done by having a tiny UTF-8 codepoint recognizing FSM in your lexer (UTF-8 is a self-synchronizing code, which makes this part easier) and ignoring the existence of graphemes.
Most people, however, shy away from implementing a parser "all the way down to the bytes" and properly handling UTF-8 as a formal language. Most lean on a lexer abstracting this away. Ditto for context-sensitivity.
Recently Rust's regex engine underwent a major overhaul, and burntsushi wrote a blog post[0] about doing the "all the way to the bytes" thing in the new regex engine, I highly recommend the read:
[0] https://blog.burntsushi.net/regex-internals/#nfa-optimization-minimal-utf-8-automata
I'm referring to #2 there: "In ASCII compatible mode, Unicode character classes are not allowed."
> Pretty much all string processing I've built up in Rust over the past ten years is based on the idea of not validating up front. It's too costly to do. There's an entire section in my blog post on bstr about this: https://blog.burntsushi.net/bstr/#motivation-based-on-perfor...
Yeah I read it; it's very good! (like everything you write) I think you're right that you can't handwave away the validation overhead, and that in some cases it matters. I just think ripgrep is pretty niche: there's gazillions of websites out there and like, 10 greps. If you're writing a grep and you want to optimize the shit out of it, well welcome to working around not validating. Otherwise, the safety tradeoffs and weirdo lack of specification aren't worth it.
For anyone interested there're a few such tools that are modern alternatives to traditional Unix utilities. Interestingly all written in Rust.
grep -> ripgrep (command `rg`)
find -> fd
sed -> sd
diff -> delta
ls -> exa && lsd
cat -> bat
wc -> cw
du -> dust
ps -> procs
top -> ytop (**edit** archived; author recommends `bottom`)
They all offer their own modern take but I'll say the first two, which are the only ones I'm using, are the most interesting. Both ripgrep and fd offer easier usage. The ripgrep is also an ack replacement and fd can also replace parallel (even has compatible syntax). The amazing part is that both are an order of magnitude faster their counterparts. The ripgrep dev has extended analysis for how this is achieved on their blog[0].
[0]: https://blog.burntsushi.net/ripgrep/
My position is articulated here, with real examples: https://burntsushi.net/unwrap/
> Just to be clear, you are making the argument that when a library call detects an error of the form of unexpected NULL/not-NULL, that they abort immediately?
There's no blanket answer here because your scenario isn't specific enough. Is the pointer caller provided? Is the pointer entirely an internal detail whose invariant is managed internally? Is this pointer access important for perf? Is the pointer invariant encapsulated by something else?
Instead, I suggest showing examples of C libraries following your philosophy. Then we'll have something concrete.
In the comment I linked, you'll noticed that I actually looked at and reviewed real code examples. Maybe you could engage with those.