[Pitch N+1] Typed Throws

Hi all,

Thanks for all of the great discussion! I've revised the proposal in a number of ways based on the discussion here, although there is no way I've captured every idea of viewpoint in such a large thread. The general shape of the proposal remains the same, and much of the change is "move a significant piece out to a separate proposal". If you read the last iteration of the proposal, I suggest instead considering the changelist:

  • Move the the typed rethrows feature out of this proposal, and into Alternatives Considered. Once we gain more experience with typed throws, we can decide what to do with rethrows.
  • Expand the discussion on allowing all uninhabited error types to mean "non-throwing".
  • Provide a better example for inferring Error conformance on generic parameters.
  • Move the replacement of rethrows in the standard library with typed throws into "Future Directions", because it is large enough that it needs a separate proposal.
  • Move the concurrency library changes for typed throws into "Future Directions", because it is large enough that it needs a separate proposal.
  • Add an extended example of replacing the need for rethrows(unsafe) with typed throws.
  • Provide a more significant example of opaque thrown errors that makes use of Either internally.

Here is the full proposal text.

Doug

21 Likes

Given the unapproachable size of the current thread (261 posts!) and the change in scope of this new pitch, wouldn't it be a good idea to start a new thread about it to recenter the discussion?

I don’t follow. Isn’t @discardableResult about being able to pretend a function that returns a value were a Void function instead? Maybe I didn’t communicate it clearly.

I’d say the main advantage is that it could exist now, in a world without typed throws, and could be used to mark your API for consumers of it to know what errors the API expects them to handle while not painting yourself into a corner with a type that can’t be changed later as your library evolves.

I thought it might meet the needs of that segment of folks wanting that information to be ā€˜baked in’ to the API, while still keeping the throws as any Error for simplicity. I’m no expert here, so I could be way off base.

I posted here because I'm mostly moving pieces out to Future Directions and adding clarifications for things that came up in this thread. It doesn't seem different enough to kick off a new pitch thread where I expect folks to go and read a significant-different proposal, especially given the length of the document.

Doug

3 Likes

I’m just coming back to this thread over a week later, so apologies if this was already discussed and I just missed it, but:
DispatchQueue.sync already uses a weird hack that shouldn’t work to be rethrows. How would typed throwing affect that?

1 Like

Based on discussion here, I just added a section with an example on how typed throws helps with this problem.

Doug

3 Likes

Ah, that’s great! It’s intuitive at the source level but what ABI impact would it have? Adding a new generic type argument isn’t an ABI-stable change, is it?

1 Like

No, adding a new generic type argument is not ABI-stable. We can manage the issue in the standard library.

Doug

2 Likes

How are you not "painting yourself in a corner"?

Changing

@requiresHandlerFor(FooError)
func foo() throws {}

to

@requiresHandlerFor(BarError)
func foo() throws {}

is as source-breaking as it would be with typed throws.

1 Like

I could not find this raised (but I'm sorry if this is already discussed). Should we explicitly write catch any Error to prevent future source breakage?

I suspect there can be some possibility to introduce errorUnion(CatError, KidError) and make error this type as written in the proposal.

Rationale: While it would be possible to compute a more precise "union" type of different error types, doing so is potentially an expensive operation at compile time and run time, as well as being harder for the programmer to reason about. If in the future it becomes important to tighten up the error types, that could be done in a mostly source-compatible manner.

I don't know what 'mostly source-compatible manner' points, but I found the following breakage case. As written in the proposal, when multiple try occurs in the same do clause, they become any Error in the catch clause. However, if we introduce errorUnion, the catch clause will implicitly get errorUnion(CatError, KidError) which is not convertible with GenericError .

do {
  try callCat() // throws CatError
  try callKids() // throw KidError
} catch {
  var e = error
  e = GenericError(message: "")
}

I think it's possible to consider adding explicit any Error notation to avoid such breakage.

do {
  try callCat() // throws CatError
  try callKids() // throw KidError
} catch any Error {  // or `let error as any Error`
  // implicit 'error' variable has type 'any Error'
}

I have to admit that it is redundant for now, and there are pros and cons, but I think it needs to be considered.

1 Like

I am unreasonably excited to be able to throw members of an error type without needing to type out the error type each time :heart_eyes:

btw, there is an incomplete sentence towards the end of Catching typed thrown errors:

Note that the only way to write an exhaustive do...catch statement is to have an unconditional catch block. The dynamic checking provided

[Edit] Another errata in Standard library operations that rethrow :

public func map<U, E>(
  _ transform: (Wrapped) throws(E) -> U
) rethrows(E) -> U?

… should be

public func map<U, E>(
  _ transform: (Wrapped) throws(E) -> U
) throws(E) -> U?

… right?

Just want to quickly chime in and say that I love every detail of this proposal! :heart: I think it’s very balanced and adds the #1 feature I currently miss in Swift. :100:

The only thing I think could be improved is the ā€œwhen (not) to useā€ section which does not explicitly mention which group iOS/macOS apps are in. While it’s clear to me that it’s basically a module that will not be integrated into another one and therefore is a great place to use typed throws, it might not be clear to less informed developers and mentioning iOS apps explicitly could help clarify things.

Also, the section states that in most cases it does not make sense to use them. But app development being a huge use case for Swift, I kinda disagree as it can make a lot of sense to use typed throws in app modules and – at least in my bubble – most developers use Swift in app projects. Only a minority (outside Apple) writes Swift packages or other kind of libs from my experience.

But that’s a detail that doesn’t bother me much. I really hope this will become an official proposal soon so we can get it in the next Swift release! :crossed_fingers:

2 Likes

I'm really hopeful that Foundation APIs and other major frameworks can adopt typed throws. I think that will slowly trickle up the layers, making existential throws less necessary and less common. To echo Jon Shier's earlier comment, erasing to any Error is an escape hatch that's (unfortunately) the only reasonable choice today. I look forward to having access to explicitness — top to bottom — without resorting to Result.

1 Like

I'm sure this will be discussed for many years to come, but "top-to-bottom typed throws" is not recommended by the typed throws proposal.

Doug

1 Like

Any time a user (whether end user or library user) is in some way responsible for the error and / or needs to (or could) do something to resolve it in a subsequent action the possibility of this error should be clearly documented so apps may handle it and provide useful information to the user. I have encountered countless times as both an end user and a library user where this information is absent or very difficult to find. This results in a needlessly frustrating end user experience.

This could provide a useful heuristic for deciding when to use typed errors. The only alternative is providing this information via documentation. My experience has been that libraries that do this well are rare. Establishing this as a conventional heuristic for typed throws would encourage good behavior throughout the ecosystem.

A good example is credit card expiration. Apps do not always report the reason for payment failure even when this information is available to the payment provider. Providing typed errors that clearly represent the reason for failure would encourage better behavior and be very helpful to end users.

4 Likes

I think it's best we just wait and see, now. We all need real-world experience to inform our opinions any further.

I've been following @Douglas_Gregor's implementation of this and it's exciting to see it coming along, although I have no idea how far away it is from being integrated. It might lead to my first real use of the Swift nightly build, though, once it's in there - I'm very interested to see how it plays out in practice in a couple of projects I'm currently working on.

So kudos [again] to @Douglas_Gregor for working on this.

2 Likes

You don't need typed throws to give descriptive error messages to users.

4 Likes

When I first heard of this proposal I didn’t like the sound of it because I was weary of having to deal with a more complex error type system like other languages. But after reading the proposal and most of the comments here it really seems like a big improvement especially with the ability to have one type for a throwing vs non throwing class like everyone is mentioning. And it doesn’t bleed through and cause all functions to need to be typed.

So big +1 from me

2 Likes

Any timeline on when we might be able to use Typed Throws with standard Xcode Swift Toolchain and ship it in an iOS (16) app? Beginning of next year? Or post WWDC?

1 Like

Typed throws is under review, and the Language Steering Group has not yet posted their decision on it. It's being implemented on main, so the earliest it could make it into into a released compiler would be Fall 2024. Snapshots of main already have it under an experimental flag TypedThrows, and can be downloaded from swift.org.

Doug

11 Likes