error: failed to read contents of `foo`
caused by: a physical I/O error has occurred (...)
and error: failed to open `foo`
caused by: permission denied (...)
are significantly more actionable than just the underlying I/O errors by themselves. To the point where a person reading these messages has a shot at addressing the problem without any prior knowledge of the code base.In `anyhow` adding such context via its `context` family of methods seems fairly well accepted. I hope that my comment demonstrates there's no reason why `enum` based approach would need to be any worse than what `anyhow` can achieve.
(emphasis mine)
This is a huge belief of mine as well. I call it a "semantic stacktrace".
I actually go as far as to say that _most of the time_ backtraces in errors are an antipattern, as it gives a false sense that an error is actionable ("oh, just look in the code!").
I even try to make it such that each leaf error is constructed in exactly one place so that grepping for the error identifies that one location.
This is a point of debate[1] among the error-handling working group.
[1]: https://github.com/rust-lang/project-error-handling/issues/4...
I don't think you really would treat a failure in File::open and read_to_string() differently in most applications. If you have a function doing file IO you might retry reading a read_to_string() error, but you know that the file exists because you know which function you just called.
If you're at higher level of abstraction, where something is calling both open() and read_to_string() for you, are you really going to handle it differently that frequently whether it's a missing file or a file on an unavailable storage medium?