SE-0413: Typed throws

A big +1. This is an excellent proposal. I appreciate its technical care. I appreciate its attention to language ergonomics. I appreciate its motivating examples and thoughtfully stated rationale. And I especially appreciate how carefully it translates that rationale into recommendations about ongoing best practice.

Some SE proposals are “Let’s see what people do with it!” proposals. Not this one. Swift developer norms — culture and habit — are going to be a crucial part of making this new feature play out well in practice, and it’s good that this proposal maps out what those norms should look like as a part of making its case. I’ve spent serious time living with Java's unhappy attempts to make error types explicit, and I do not relish a return to the world of throw MyMeaninglessWrapperException(​exceptionThatIsMeaninglessToMe). But that unhappy world is a product of how the language gets used, not the feature itself. The proposal puts it beautifully:

Errors are generally propagated and rendered, but rarely handled exhaustively, and are prone to changing over time in a way that types are not.

With that in mind, I appreciate and wholeheartedly agree with the When to use typed throws section. I hope those thoughts make it into both official Swift language documentation and general Swift developer parlance. Those guidelines answer my concerns about repeating the mistakes of the Java model.

As far as the language design substance of the proposal, everything seems entirely spot on. (The subtyping rules make me slightly nervous, but they’re clearly necessary and I can’t spot any problems.)

I particularly appreciate the way this addresses generality gaps in rethrows, a Swift language feature that’s always had a bad smell for me. This proposal fully addresses my concerns with it. (I wonder whether we might deprecate rethrows entirely in the future. Maybe. Let’s see how things play out.)

Nice work!

A few specific thoughts:

  • I do not share the concerns expressed by others about the parentheses. The throws(Oops) syntax reads just fine to me. Even if it weren’t for the compiler parsing problems, in casual experiments, dropping the parens if anything makes human parsing a bit harder to my eye — especially when the error type is not a simple type. The proposed syntax is good as is.

  • This is probably going to cause large headaches for a small number of people, both on the Swift 5 and the Swift 6 sides of the wall:

    To prevent this from becoming a source compatibility problem, we apply the same rule as for do...catch statements to limit inference: throw statements within the closure body are treated as having the type any Error in Swift 5.

    I don’t see a way around it, but those error messages (and fixits?) are going to need some very special attention. That’s a confusing rabbit hole right there.

  • There’s some useful guidance in the proposal on when to use some Error versus any Error. Make sure that too ends up in the language guide.

  • There was extensive discussion during the pitch phase about introducing (untagged) sum types to Swift as a way of letting functions throw multiple types, as “Alternatives considered” briefly mentions. I’m skeptical of this direction, and happy that we are not proposing it at this time. (I’m similarly happy that we are not proposing multiple thrown types, which are just sum types in disguise, for exactly the reasons the proposal mentions.)

    Why don’t we need sum types right away? I’m looking at the proposal’s guidance, which is solid, on when typed throws is appropriate:

    1. Code that stays within a module or package, where the caller always fully handles the error.
    2. In generic code that passes through errors from other components.
    3. Dependency-free code that is meant to be used in a constrained environment.

    Items 1 and 3 both imply a closed world of known possibilities, in which an explicit enum of possible error cases is entirely appropriate. Item 2 is just the transitive closure of cases 1 and 3.

    Given that, I only see two likely reasons why type throws would make sum types compelling:

    • Typescript developers craving Typescript familiarity.
    • Failure to heed the proposal’s recommendations about when typed throws is appropriate, leading to a need for sort-of-source-stable retrofitting of a new error type in an existing API.

    If we use this new feature wisely, I think we’ll find that we rarely want multiple thrown types, and when we do find ourselves wanting that, it’s a sign that an enum is the proper design choice. (Note that Typescript’s design constraint of retrofitting untyped Javascript APIs puts it in a position where tagged unions can’t work. We don’t have that problem in Swift unless we choose to have it!)

    We’ve already put tremendous effort into thinking about the interaction of enum with library evolution. Let’s put that work to use with typed throws, live with the feature for a while, and then reassess whether multiple thrown types or untagged sum types actually solve a real problem.

Again, great work on the proposal.

14 Likes