Rust already has most of what I want from error handling: explicit control flow, errors as values, and concise propagation with ?. The friction comes when deciding what to put in the error half of Result. We often end up choosing between precise types that require boilerplate and convenient types that hide which errors can occur. But precision and convenience do not have to be competing goals. Error types should compose as easily as the functions that return them.
This is super-cool and I’m very tempted to go try and apply this to a few projects. (But uh I have to do “real work” now.)
One possible downside I see: when you have to craft container error types or manually convert among them, you are kinda forced to think about context propagation. A bare “FileNotFound” error needs to be annotated from the caller to indicate which file was not found, or otherwise the message shown to the user will be near-useless. Merely being able to propagate io::Error doesn’t carry this friction, so it’ll be propagated without details.
Now, crafting container error types doesn’t solve the problem above and it’s still possible to end up with insufficiently-specified errors. But at least it offers a sort of checkpoint. @mcmah309, what is your experience with this?
If I understand correctly, this sounds like an issue regardless of using eros or not. You are saying io:Error does not carry all the information you need? In that case, wouldn’t wrapping in a container type be enough – e.g. FileNotFound(io::Error, String) (or even deconstructing the io:Error to whatever shape you want). This is what I would do if I had a recovery tool or message tool up the call stack that could do something based on that type. But if you just want to propagate the information, I wouldn’t expose a plain io:Error if it is not actionable, but either way you could just add context .with_context(|| format!("Reading file {file_name}")). Depending on the recipient of this you could use .with_user_context instead. Another alternative is using the #[context] macro. See https://github.com/mcmah309/eros#context-macro and https://github.com/mcmah309/eros#context-placement-two-approaches
This is super-cool and I’m very tempted to go try and apply this to a few projects. (But uh I have to do “real work” now.)
One possible downside I see: when you have to craft container error types or manually convert among them, you are kinda forced to think about context propagation. A bare “FileNotFound” error needs to be annotated from the caller to indicate which file was not found, or otherwise the message shown to the user will be near-useless. Merely being able to propagate io::Error doesn’t carry this friction, so it’ll be propagated without details.
Now, crafting container error types doesn’t solve the problem above and it’s still possible to end up with insufficiently-specified errors. But at least it offers a sort of checkpoint. @mcmah309, what is your experience with this?
score 1 · canonical
If I understand correctly, this sounds like an issue regardless of using eros or not. You are saying io:Error does not carry all the information you need? In that case, wouldn’t wrapping in a container type be enough – e.g.
FileNotFound(io::Error, String)(or even deconstructing the io:Error to whatever shape you want). This is what I would do if I had a recovery tool or message tool up the call stack that could do something based on that type. But if you just want to propagate the information, I wouldn’t expose a plain io:Error if it is not actionable, but either way you could just add context.with_context(|| format!("Reading file {file_name}")). Depending on the recipient of this you could use.with_user_contextinstead. Another alternative is using the#[context]macro. See https://github.com/mcmah309/eros#context-macro and https://github.com/mcmah309/eros#context-placement-two-approachesscore 1 · canonical