[swift-evolution] [Pitch] Typed throws

John McCall rjmccall at apple.com
Tue Feb 21 00:29:22 CST 2017


> On Feb 20, 2017, at 4:15 PM, Joe Groff <jgroff at apple.com> wrote:
>> On Feb 20, 2017, at 9:57 AM, John McCall via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> On Feb 19, 2017, at 3:04 PM, Anton Zhilin via swift-evolution <swift-evolution at swift.org <mailto: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.

I definitely agree with the documentational benefits of declaring interesting error cases, and I agree that most libraries will add new domain-specific errors, too.  I just don't see the actual benefit to either API authors or clients of saying "I will only throw errors that look like *this*" and then *this* turns out to be an enum with ten interesting cases, most of which don't apply to the specific operation, and a bunch of generic wrapping cases that communicate very little; especially not compared to saying "in this interesting case I'll throw a FooError.bar" in the documentation.  Especially since it's not obvious that *cases* are the right granularity for describing a specific kind of domain error, since a case can't be retroactively split into more- and less-informative sub-cases without adding language features that only I seem to be interested in. :)

> 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.

To be exact, resilience prevents it from being a permanent liability as long as you've thought ahead enough to use your own error type.

> 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.

I agree that, if we added typed throws, there would be an immediate demand for enum subtyping as a way to make remapping less painful.  We'd also want some way of allowing users to specify an *explicit* remapping function (presumably a case constructor in most cases); this could probably be a library function, though.

John.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170221/7952f20a/attachment.html>


More information about the swift-evolution mailing list