[swift-evolution] typed throws

Karl Wagner razielim at gmail.com
Sun Aug 20 17:29:14 CDT 2017

> On 18. Aug 2017, at 08:27, John McCall via swift-evolution <swift-evolution at swift.org> wrote:
>> On Aug 18, 2017, at 12:58 AM, Chris Lattner via swift-evolution <swift-evolution at swift.org> wrote:
>> Splitting this off into its own thread:
>>> On Aug 17, 2017, at 7:39 PM, Matthew Johnson <matthew at anandabits.com> wrote:
>>> One related topic that isn’t discussed is type errors.  Many third party libraries use a Result type with typed errors.  Moving to an async / await model without also introducing typed errors into Swift would require giving up something that is highly valued by many Swift developers.  Maybe Swift 5 is the right time to tackle typed errors as well.  I would be happy to help with design and drafting a proposal but would need collaborators on the implementation side.
>> Typed throws is something we need to settle one way or the other, and I agree it would be nice to do that in the Swift 5 cycle.
>> For the purposes of this sub-discussion, I think there are three kinds of code to think about: 
>> 1) large scale API like Cocoa which evolve (adding significant functionality) over the course of many years and can’t break clients. 
>> 2) the public API of shared swiftpm packages, whose lifecycle may rise and fall - being obsoleted and replaced by better packages if they encounter a design problem.  
>> 3) internal APIs and applications, which are easy to change because the implementations and clients of the APIs are owned by the same people.
>> These each have different sorts of concerns, and we hope that something can start out as #3 but work its way up the stack gracefully.
>> Here is where I think things stand on it:
>> - There is consensus that untyped throws is the right thing for a large scale API like Cocoa.  NSError is effectively proven here.  Even if typed throws is introduced, Apple is unlikely to adopt it in their APIs for this reason.
>> - There is consensus that untyped throws is the right default for people to reach for for public package (#2).
>> - There is consensus that Java and other systems that encourage lists of throws error types lead to problematic APIs for a variety of reasons.
>> - There is disagreement about whether internal APIs (#3) should use it.  It seems perfect to be able to write exhaustive catches in this situation, since everything in knowable. OTOH, this could encourage abuse of error handling in cases where you really should return an enum instead of using throws.
>> - Some people are concerned that introducing typed throws would cause people to reach for it instead of using untyped throws for public package APIs.
> Even for non-public code.  The only practical merit of typed throws I have ever seen someone demonstrate is that it would let them use contextual lookup in a throw or catch. People always say "I'll be able to exhaustively switch over my errors", and then I ask them to show me where they want to do that, and they show me something that just logs the error, which of course does not require typed throws.  Every.  Single.  Time.
> Sometimes we then go on to have a conversation about wrapping errors in other error types, and that can be interesting, but now we're talking about adding a big, messy feature just to get "safety" guarantees for a fairly minor need.
> Programmers often have an instinct to obsess over error taxonomies that is very rarely directed at solving any real problem; it is just self-imposed busy-work.
>> - Some people think that while it might be useful in some narrow cases, the utility isn’t high enough to justify making the language more complex (complexity that would intrude on the APIs of result types, futures, etc)
>> I’m sure there are other points in the discussion that I’m forgetting.
>> One thing that I’m personally very concerned about is in the systems programming domain.  Systems code is sort of the classic example of code that is low-level enough and finely specified enough that there are lots of knowable things, including the failure modes.
> Here we are using "systems" to mean "embedded systems and kernels".  And frankly even a kernel is a large enough system that they don't want to exhaustively switch over failures; they just want the static guarantees that go along with a constrained error type.
>> Beyond expressivity though, our current model involves boxing thrown values into an Error existential, something that forces an implicit memory allocation when the value is large. Unless this is fixed, I’m very concerned that we’ll end up with a situation where certain kinds of systems code (i.e., that which cares about real time guarantees) will not be able to use error handling at all.  
>> JohnMC has some ideas on how to change code generation for ‘throws’ to avoid this problem, but I don’t understand his ideas enough to know if they are practical and likely to happen or not.
> Essentially, you give Error a tagged-pointer representation to allow payload-less errors on non-generic error types to be allocated globally, and then you can (1) tell people to not throw errors that require allocation if it's vital to avoid allocation (just like we would tell them today not to construct classes or indirect enum cases) and (2) allow a special global payload-less error to be substituted if error allocation fails.
> Of course, we could also say that systems code is required to use a typed-throws feature that we add down the line for their purposes.  Or just tell them to not use payloads.  Or force them to constrain their error types to fit within some given size.  (Note that obsessive error taxonomies tend to end up with a bunch of indirect enum cases anyway, because they get recursive, so the allocation problem is very real whatever we do.)
> John.
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>

I’m really happy with the way this discussion has changed since last time. This is pretty much the same point I was advocating back then.

When people say they want typed-throws, I don’t think they’re talking about optimisation. Errors are really for exception cases - if you expect it to be thrown in normal program flow, it’s really a return value and you should be using an enum directly. Besides, if the compiler has enough knowledge about which errors are being thrown, there’s no reason it can’t eliminate or further optimise that boxing behind-the-scenes (at least within the same module). So I remain unconvinced that we absolutely need typed-throws for Error optimisation.

The key is that compiler knowledge. Within a single module, we should be able to trace the ‘throw X’ statements and annotate each function/closure with a list of potentially-thrown errors. We already do checking of throwing scopes to make sure every throw is handled; conceptually we could also produce a list of error-sources.

Once the compiler has this information, we could provide a lot of benefits for free:

- Within a single module, we could eliminate/optimise boxing and do exhaustive catching with no code changes.
- For lazy library authors (which we all, occasionally, are) we could automatically document the publicly-visible errors thrown by their public functions (with an opt-out switch).
- For diligent library authors, we could check their more descriptive error documentation and warn them if they missed any cases or invented ones that are never thrown.
- That documentation could be parsed by the compiler to provide autocomplete across libraries. We would extend our markdown documentation syntax a little to accommodate.

And that, I think, really encapsulates the core of what people want from a full strongly-typed throws. Better documentation about thrown errors, which the compiler by default ensures is reliable, and autocomplete when catching.

- Karl

P.S: We would still box Errors across modules, for resilience. I’m not sure that it’s worth allowing breaking that, because it locks your implementation in so much you might as well make the function @inlineable.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170821/7a1375ce/attachment.html>

More information about the swift-evolution mailing list