• ErrorUnion is an open sum type.

    I was a bit confused whether this was an existing Rust feature or a “new invention”.

    Is it built using Rust unions? (Something I don’t think I’ve ever seen used.)

    It feels adjacent to Roc’s open tag unions?


    score 1 · canonical

    • OCaml has this approach to errors built-in thanks do its polymorphic variants feature. The downside in my experience is that when one can more easily make unions between sets of errors one will take that approach. Without that point where the programmer is given a moment to explicitly design the error it tends to end up under-designed, lacking any additional useful context that could have been added otherwise.


      score 1 · canonical

        • Well that mean incremental compilation is still fast. But look at the numbers, first time compilation is apparently taking 2-4x as long compared to anyhow. And I think if you want a more like-for-like comparison you should look at thiserror rather than anyhow. And I would also very much interested to see how the compile time grows w.r.t. number of error types in the union.


          score 1 · canonical

      • To my inexpert eye it looks like the subset search (the aforementioned unordered set trick) is quadratic to solve — but at least not exponential, and perhaps not a problem with a typical number of error types in the set? (I also see it’s arbitrarily limited to 26 error types, because the tuple generics have to be done explicitly.)


        score 1 · canonical

    • Author here. The only downside in my opinion is that is does not work on no_std without alloc. But even this I just figured out with the inspiration of a colleague. Hopefully a PR for this will be up in the coming days – A lot of pieces to get right and conditional compilation, but the api will be the same. Overall for eros, I meticulously designed the api and am very happy with it. Full flexibility to the developer.

      Yes there is an anyhow adapter behind the anyhow feature flag https://github.com/mcmah309/eros#anyhow


      score 1 · canonical

  • Wow, this is very cool, thanks so much for sharing! I’ve had this exact frustration with error types before and this seems like a really nice way to handle it. The ability to define errors as a small set of them that actually can occur at a given call instead of packing it into module-level enum with irrelevant errors is so nice for being able to refine what actually happens. I’m very excited to give this a go!

    Another annoyance that I’ve run into for error handling is that it is annoying to write code that returns multiple errors. Say you’re writing a backend CRUD application and you have to verify the fields of a create request. It would be nice to be able to return all fields that were in violation of the constraints rather than just the one, but it’s difficult to do that with Result since ? returns early. I’ve seen some solutions using some sort of error vec crate, but they’ve never felt very elegant to me. I’m curious to see if some constructs in this crate have any hints at a solution.


    score 1 · canonical

  • 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_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


      score 1 · canonical

  • exposing ParseIntError in this way feels iffy: this isn’t a failure to parse just any int, this is a failure to parse a port in particular. suppose your address contains two ints (maybe you’re connecting to a redis db and you have a port and a db number): now ParseIntError is ambiguous. this breaks either ? or automatic widening: now you need to specify which particular int failed to parse, by doing something like .map_err(ParsePortError) manually (just #[context] won’t do if you want to change this programmatically, because #[context] doesn’t change types).

    for that reason, I think impls like From<ParseIntError> for ConnectionError are anti-patterns, and generally you should try to wrap “low-level” errors like these into more semantic types (or variants, you can have ConnectionError::ParsePort(ParseIntError) and ConnectionError::ParseDb(ParseIntError)). would be nice to see some infrastructure for that, although I’m not sure if it’s doable without some heavy macro use.

    (a separate question is whether you should expose underlying errors at all in any particular case: maybe redis will transition to named dbs, and you’d wish you just left that error opaque)


    score 1 · canonical

    • I agree with your assertion. The example situation was not the best. It was more there to show the flow/api of eros. In real code don’t expose ParseIntError unless the caller cares about that exact error type. The 1. philosophy in the README is listed as “Error types only matter when the caller cares about the type, otherwise this just hinders ergonomics and creates unnecessary noise.”


      score 1 · canonical

        • That is one option, not the only. The whole point would be “expose this type of error if something downstream is actionable based on the type”. Even if you went with an enum error handling approach you would still run into this dilemma if you want to handle “correctly”. So yes, a better identifying type for this type than ParseIntError would be ideal. You could still choose to go the untyped route and downcast as well. If you own the domain, this certainly is not “wrong”. If the error recovery path for every error is “fallback to a default port”, then you really don’t have to go the newtype route at all. But if there is multiple error recovery paths, then e.g. a PortSetError may be ideal.


          score 1 · canonical