Errs is usually more rare than constructing the
Ok variant, because most operations succeed, don't fail. So the runtime performance of error handling is usually not critical, or not even relevant at all.
In the case of
ripgrep, the tool gets its enormous performance advantage over classical
grep by parallelizing search and by doing less work (ignoring hidden/
.gitignore'd files) by default. The time spent in constructing
Results is probably several orders of magnitude smaller than constructing and executing state machines, not to mention reading all those files from disk…
I don't think there are 3 approaches. "Wrapping errors" and "defining your own errors" is essentially the same thing: non-boxed errors. From this point of view, it doesn't matter whether your
enum Error contains associated data from other crates or you make it hold types purely from your own code. It's still all statically-typed without additional heap allocation.
The usual trade-off between the two approaches is that using a boxed trait object loses typing information, so it's easier on the implementor and less useful for the user, meanwhile preserving full static types requires writing slightly more boilerplate code on the implementor's part, while being somewhat more useful to the user.
I have previously explained my views w.r.t. very strongly-typed error handling being somewhat overrated in the Rust community; apart from
std::io::ErrorKind::Interrupted I have never seen any real use-case where it would have been possible (let alone necessary) to act differently on different kinds of errors coming from a given, narrow-focused crate, apart from printing, bubbling, or unwrapping it.
Therefore, while somewhat more elegant in principle, I don't think you have to necessarily define your own error
enum. Creating a newtype around a
Box<dyn Error> can still be genuinely useful, though, because it allows other authors of downstream crates to provide
From impls based on your error type.