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?
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...
This means that errors, if implemented as described in this post, either formatted or handled don't give sufficient information to the caller/user on how to deal with the error.
EDIT:
I strongly recommend to use `op.map_err(|e| SomeError::Open(e, filename))?` and `op.map_err(SomeError::Read)?` as an alternative when propagating the errors. It is more typing at the location of propagation than just `?`, but the errors this approach produces are immediately actionable regardless of whether they are printed to the user or handled by a caller. Provided, of course, this pattern is applied consistently.