Thank you!
I've said it before, but I want to reiterate that this has been a particularly insightful and educational discussion. For me at least. But I suspect others also didn't know about a lot of these use cases, or a lot of the techniques people have developed.
The Original Error Handling Handler
I've been re-reading the original Error Handling Rationale documentation, by @John_McCall (I thinkā¦? Git history suggests so). From circa Swift 2.
I actually don't recall it being mentioned before in this thread, so for anyone who hasn't read it, it's well worth it. It's very well written and although nominally for the Swift team itself, I find it very approachable to the lay-programmer.
It's particularly amusing to read its section on "Typed propagation" where it lays out the argument for why Swift should require marking of throwing functions at all (the throws
keyword in function declarations, to be clear). And similarly why try
should be required. Of course now these are all just givens and obviously wise design choices, but keep in mind that at the time it was written no mainstream programming language adopted that approach (to my knowledge).
Specificity of Typed Throws
And there's one subset of that in particular that I think is relevant to the topic of typed throws:
The alternative is to say that, by default, functions are not being able to generate errors. This agrees with what I'm assuming is the most common case. In terms of resilience, it means expecting users to think more carefully about which functions can generate errors before publishing an API; but this is similar to how Swift already asks them to think carefully about types. Also, they'll have at least added the right set of annotations for their initial implementation. So I believe this is a reasonable alternative.
But then:
The last question is how specific the typing should be: should a function be able to state the specific classes of errors it produces, or should the annotation be merely boolean?
Experience with Java suggests that getting over-specific with exception types doesn't really work out for the best. It's useful to be able to recognize specific classes of error, but libraries generally want to reserve flexibility about the exact kind of error they produceā¦
I find it incongruent with itself. It uses essentially the same argument - "this requires forethought and mistakes may be locked in forever" - to argue both sides. In the first, that functions should have to opt into throwing if they ever might want to throw, but then in the second that functions should not specify what they actually throw because then they might be unable to change it.
I think it's fair to insist on consistency here. Either we err on the side of flexibility - removing the throws
keyword so that functions may change their mind in future - or we err on the side of programmer empowerment - allowing them to trade that flexibility for specificity (or other benefits, like eliminating runtime exception-handling costs, or existentials).
In fairness to that original document, it does go on to present other arguments too (e.g. the problem with wrappings, that's also been raised in this thread). I'm not going to reiterate or re-debate those here, as I think they're already well-covered within this discussion thread.
Really, what I'm getting at in bringing this up again is that for written guidance on when to used typed throws and on what type to choose, I really encourage everyone to focus on providing rationale, not dogma. Just as in a good style guide, a persuasive argument for a guideline is much more useful than a blunt "rule".
The current proposal draft is pretty good in this respect - When to use typed throws and Effect on API resilience are both mostly well-written explanations of the trade-offs and pitfalls (if a little incomplete). But the former does lean a little bit into prescriptivism with statements like "The loadBytes(from:)
function is not a good candidate for typed throws" and "Typed throws is potentially applicable in the following circumstancesā¦" and then some bullet points which are somewhat but not entirely self-explanatory. I think it paints an overly-restrictive picture of when typed throws may be used. It also completely omits mention or consideration of domains like Embedded Swift where typed throws might be appropriate in a broader range of circumstances (like a loadBytes(from:)
method).
I expect these sections of the proposal will also form the basis of updates to the Swift Language Guide, which is where the audience gets much bigger and this really matters.
I almost didn't bring this up, because it feels like it might appear a bit nitpicky, but I do think it's important to write with reader agency in mind and focus on education more than codification. Especially for things written by Swift team members or in official Swift documentation, because their position imbues a degree of authority whether intended or not.