Typed throw functions

Sure - so my issue with typed throws isn't so much with the specific feature itself. Error is just a marker protocol, and by itself is no more useful than Any. You wouldn't write an API taking Any parameters and returning Any results, so it makes writing code (especially code which actually handles errors) more awkward to write as you have to claw back that type information via dynamic casts and handle failure cases which you know will never happen (especially in a single module).

That said, I think that introducing typed throws right now would lead to an overall worse error model than we currently have. There are a couple of reasons for this, mostly revolving around enums. Conceptually, enums are a really great fit for modelling errors, so it isn't surprising that those flaws really get exposed through throwing functions. Also, I agree with @Lantua that the major motivating use-case for typed throws is exhaustive catching, and that only works on enums anyway. So enums and typed throws have a very strong link.

The biggest issue is that you cannot add new cases to public enums. This is something of a flaw in the language - and has been a topic of much discussion. Essentially, it means that we shouldn't really be allowing exhaustive switch or catch on enums from other modules in the general case anyway - you should always have to handle @unknown default cases, so the enum has freedom to grow. We currently have a quirk in the language so if the enum's declaring module was compiled with library-evolution, it will actually be extensible and cannot be exhaustively switched/caught unless you opt-in to that. The thing is - it's also a massive issue for source stability, not just binary compatibility. Unfortunately, there isn't even a way for enums in regular modules to opt-in to that very important flexibility (that's what the proposal I linked to aims to address).

So why is this a problem? Well, let's take your example:

public enum AuthenticationError: Error {
    case invalidCredentials
    case unknownReason
}

public func authenticate(with username: String, and password: String) throws AuthenticationError

Let's imagine this is part of a library - not even an ABI-stable library, it can even be shipped as source. Now, you've had the foresight to add an unknownReason catch-all clause, which is good. Lots of developers won't think of that - especially if they've never run in to the issue themselves or frequent these forums to have heard about it. But it won't save you ;)

So they ship the above code in v1.0 - but soon after, they notice some suspect behaviour, and decide to rate-limit the authentication requests. Now, this company has really poor frontend caches, so they recommend (but don't require) that Apps handle this specific error and enforce the limit on the client side. Even with an unknownReason case though, we still don't have any ability to add errors to the function, other than bumping it up to v2.0. This creates yet further headaches with package dependency resolution, as all dependencies must be compatible with the same major version of the library.

So you can see how enums make this feature impossible to use without limiting how your function's implementation can evolve, even if it isn't @inlinable. That's why I think it's important to fix enums before we give them even more significance by exposing them in this way.


So that's an overview of the problem. Will it be an actual issue for developers in the real world? I think it will. Just looking at this thread:

It sounds very much like people think that typed throws is something that they "should" do. Of course, it would eliminate some boilerplate, but in many cases, it could inadvertently expose some non-obvious pitfalls in the language's library evolution story (both for ABI and source stability).

In general, I agree most with the sentiments expressed by @John_McCall:

P.S. For @Chris_Lattner3: Are those functions public and are the errors enum types? If so, is this person aware of the evolution constraints?