[swift-evolution] [Pitch] Typed throws
jgroff at apple.com
Tue Feb 21 10:18:25 CST 2017
> On Feb 20, 2017, at 11:20 PM, Russ Bishop <xenadu at gmail.com> wrote:
>> On Feb 20, 2017, at 1:15 PM, Joe Groff via swift-evolution <swift-evolution at swift.org> wrote:
>>> On Feb 20, 2017, at 9:57 AM, John McCall via swift-evolution <swift-evolution at swift.org> wrote:
>>>> On Feb 19, 2017, at 3:04 PM, Anton Zhilin via swift-evolution <swift-evolution at swift.org> wrote:
>>>> It’s expected that if you need resilience, then you will throw an “open” enum. Essentially, we pass resilience of typed throws on to those who will hopefully establish resilience of enums.
>>>> If you prefer separate error types, then declare a base protocol for all your error types and throw a protocol existential. You won’t even need default case in switches, if closed protocols make it into the language.
>>>> I don’t like any solution that is based on comments. I think that compiler should always ignore comments.
>>> I agree. And in general, this sort of thing is exactly my core concern about adding typed throws to the language: I am completely certain that many programmers will add typed throws annotations because they're programmers and thus, well, probably a little obsessive/compulsive, and they're trying to precisely document the behavior of their function without necessarily thinking about the usefulness of that information for their clients and (if they're writing a library; and really you should almost always be writing code as if you're writing a library) whether they're actually willing to commit to that behavior in their interface. For those programmers, typed throws is just going to box them in and force them into anti-patterns in the long run.
>>> In the vast majority of use-cases, clients are not going to exhaustively handle all errors — they will always have some generic fall-back. That is not pessimism, it's actually the natural result of the complicated world we live in, where code can fail for a huge host of reasons and most callers won't have meaningful special-case behavior for all of them. (On most operating systems, opening a file or a network connection can fail because you ran out of file descriptors. You're seriously telling me that you're going to add a special case to your error logic for that?) Go look at the actual error types that people use in most typed-throws situations and try to tell me I'm wrong — they probably have like twenty alternatives, and solidly a quarter or more of them will just be embedding some other arbitrarily-complex or stringly-typed error value.
>> I agree that overly specific API specs are a concern. There's still some documentational benefit to a typed error enum, even if it ultimately ends up containing a catch-all case, since it gives you the ability to also enumerate some number of discrete, realistically handleable cases. Sure, your network request running in an XPC service has infinitely many failure cases due to resource constraints or IPC failure or network chaos, but there's usually some number of domain-specific error cases too. And if we compare the proposed "you get to specify one error enum type" model to, say, Java or C++98's "you get to specify any number of error classes" model, I think that helps steer people away from the most grievous API mistakes in Java land, since instead of listing a closed set of concrete error classes directly in your API signature, you'll list those error cases in your enum definition, and in a resilient world, the enum will be "open" by default to external users, preventing it from being a permanent liability unless the user explicitly opted into closedness.
> Realistically there are only rarely actionable error cases and usually only one or two; everything else just bubbles up to the top of the current operation where you retry or report a permanent failure. If the enum is open then you need a default catch-all error handling case anyway, so what benefit is there to typed throws that you don’t get with documentation comments?
> I’m positive John is right about what will happen in practice.
Perhaps. Doc comments have all the usual problems of documentation not being compiled code so drifting out of date with implementation. While an open error type wouldn't guarantee that the errors declared are the ones thrown, it'll at least guarantee that they exist.
>> In the discussions around Rust's error handling conventions, they recognized this pattern of APIs either raising some number of layer-appropriate errors or carrying forward the failure modes of the layers below them, and they developed a convention for errors wrapping other errors. It would be reasonable to say we should do the same thing as part of the typed errors design. If we were to generalize enum subtyping beyond Optional, that might be one way to go about it, letting an enum wrap its underlying layers' failure cases as subtypes.
> Yeah but Rust has a macro system so error-chain makes re-wrapping trivial. A simple `chain_err(|| “my new error message”)` is enough to define and “throw” a new error that wraps the underlying error.
That was my point—they recognized error chaining/layering as a common phonemenon and provided affordances for it as part of their design. I was saying we should do the same if we investigate typed errors seriously. (Not sure why you mentioned macros—chain_err is just a regular method, and could be defined as an extension method on Error in swift today.)
More information about the swift-evolution