# \[Pitch N+1\] Typed Throws

**URL:** <https://forums.swift.org/t/pitch-n-1-typed-throws/67496>\
**Category:** Pitches\
**Created:** [September 25, 2023, 4:25am UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496 "2023-09-25T04:25:05Z")\
**Posts on this page:** 20\
**Page:** 12

<div class="post-metadata">

**Author:** ![Jumhyn](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jumhyn/32/3891_2.png) [@Jumhyn](https://forums.swift.org/u/Jumhyn)\
**Post date:** [October 3, 2023, 6:55pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/222 "2023-10-03T18:55:04Z")

</div>

This isn't `Never` satisfying arbitrary requirements as a type argument, this is a different behavior related to `Never`. The compiler does not require you to have a valid return value from a function if all paths call into a `Never`-returning function. It's no different than the following:

```swift
func g() -> Int {
  fatalError()
  // ok, we'll 'Never' return from this function so don't need a return value
}

```

Trying to satisfy the requirement explicitly fails (similarly to your example where you've provided the `Never` type annotation):

```swift
protocol Foo {}

func f<T: Foo>(_: T.Type) {}

f(Never.self) // error

```

ETA:

I'm not able to reproduce this behavior:

> [@wadetregaskis](#):
>
> Although this magic isn't fully coherent - e.g. try saving the result of `foo` into a `let` constant:
> 
> ```swift
> let a = foo { fatalError("Totally fine.") }
> // Warning: Constant 'a' inferred to have type '()', which may be unexpected.
> 
> ```

When I copy/paste your code I see `T` inferred as `MyType`, and no warning related to the inferred type.

---

<div class="post-metadata">

**Author:** ![dmt](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/dmt/32/30830_2.png) [@dmt](https://forums.swift.org/u/dmt)\
**Post date:** [October 3, 2023, 7:01pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/223 "2023-10-03T19:01:28Z")

</div>

Usecases of `rethrows` we discussed so far:  
**Pass through function**

```swift
func f(g: () throws -> Void) rethrows {
  try g()
}

```

Could be expressed as:

```swift
func f<E: Error>(g: () throws(E) -> Void) throws(E) {
  try g()
}

```

This form preserves the error type, but doesn't express whether or not the same error instance will be thrown. But since `rethrows` doesn't guarantee that either, it can be considered a good enough alternative.

**`rethrows` a wrapped error**

```swift
struct WrappedError<E: Error>: Error {
  let underlyingError: E
}
func f(g: () throws -> Void) rethrows {
  do {
    try g()
  } catch {
    throw WrappedError(underlyingError: error)
  }
}

```

Could be expressed as:

```swift
func f<E: Error>(g: () throws(E) -> Void) throws(WrappedError<E>) {
  do {
    try g()
  } catch {
    throw WrappedError(underlyingError: error)
  }
}

```

**Throws an independent error, but only when a closure throws**

```swift
struct IndependentError: Error, CustomStringConvertible {
  let description: String
}
func f(g: () throws -> Void) rethrows {
  do {
    try g()
  } catch {
    throw IndependentError(description: String(describing: error))
  }
}

```

Could be expressed as:

```swift
func f(g: () throws -> Void) throws(IndependentError) {
  do {
    try g()
  } catch {
    throw IndependentError(description: String(describing: error))
  }
}
func f(g: () -> Void) {
  g()
}

```

Note: make sure it doesn't lead to ambiguity

**`rethrows` function over a typed throws closure**

```swift
func f(g: () throws(SomeError) -> Void) rethrows { ... }

```

This should be explicitly prohibited. There are not many directions to make it work, and they all are bad:

- making it typed will require union types, because otherwise it will break in case of two closures.
- erasing to `any Error` doesn't follow the principle of least astonishment, and isn't embedded-friendly.

IMO, it'd be better to explicitly prohibit combination of `rethrows` and `throws(E)` where `E` is anything but `any Error`.

**`rethrows` function as a closure with an inferred type**

```swift
func f(g: () throws -> Void) rethrows {
  try g()
}
let f1 = f // (() throws -> ()) throws -> ()

```

This one is hard. If a function already has a generic parameter - a simple transformation to typed throws can be made. But if it hasn't - then an erased variant should be provided (possibly syntesized).

```swift
func f<E: Error>(g: () throws(E) -> Void) throws(E) {
  try g()
}
func f(g: () throws -> Void) rethrows {
  try g()
}
let f1 = f // inferred (() throws -> ()) throws -> ()
let f2 = f as ((() throws(SomeError) -> Void) throws(SomeError) -> Void)

```

**`rethrows` function throws its own errors**

```swift
struct MyError: Error {}

func f(_ a: () throws -> Void) rethrows {
  func g(_ b: () throws -> Void) throws {
    throw MyError()
  }
  try g(a)
}

```

This one is an incorrect usage of `rethrows`. A version with typed throws wouldn't allow this.

---

<div class="post-metadata">

**Author:** ![Slava\_Pestov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/slava_pestov/32/30204_2.png) [@Slava\_Pestov](https://forums.swift.org/u/Slava_Pestov)\
**Post date:** [October 3, 2023, 7:16pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/224 "2023-10-03T19:16:25Z")

</div>

> [@dmt](#):
>
> **Throws an independent error, but only when a closure throws**
> 
> ```swift
> struct IndependentError: Error, CustomStringConvertible {
> let description: String
> }
> func f(g: () throws -> Void) rethrows {
> do {
> try g()
> } catch {
> throw IndependentError(description: String(describing: error))
> }
> }
> 
> ```
> 
> Could be expressed as:
> 
> ```swift
> func f(g: () throws -> Void) throws(IndependentError) {
> do {
> try g()
> } catch {
> throw IndependentError(description: String(describing: error))
> }
> }
> func f(g: () -> Void) {
> g()
> }
> 
> ```
> 
> Note: make sure it doesn't lead to ambiguity

I don't believe these two are equivalent, because `f` unconditionally throws even if `g` does not. However you can express a relationship between errors with an associated type and achieve something similar perhaps.

---

<div class="post-metadata">

**Author:** ![dmt](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/dmt/32/30830_2.png) [@dmt](https://forums.swift.org/u/dmt)\
**Post date:** [October 3, 2023, 7:20pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/225 "2023-10-03T19:20:05Z")

</div>

> [@Slava\_Pestov](#):
>
> I don't believe these two are equivalent, because `f` unconditionally throws even if `g` does not.

The second `f` accepts `g: () -> Void` which is actually `() throws(Never) -> Void`, and as `Never` is a concrete type it will be prioritized over the first `f`. Am I wrong?

---

<div class="post-metadata">

**Author:** ![Slava\_Pestov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/slava_pestov/32/30204_2.png) [@Slava\_Pestov](https://forums.swift.org/u/Slava_Pestov)\
**Post date:** [October 3, 2023, 7:21pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/226 "2023-10-03T19:21:25Z")

</div>

> [@dmt](#):
>
> **`rethrows` a wrapped error**
> 
> ```swift
> struct WrappedError<E: Error>: Error {
> let underlyingError: E
> }
> func f(g: () throws -> Void) rethrows {
> do {
> try g()
> } catch {
> throw WrappedError(underlyingError: error)
> }
> }
> 
> ```
> 
> Could be expressed as:
> 
> ```swift
> func f<E: Error>(g: () throws(E) -> Void) throws(WrappedError<E>) {
> do {
> try g()
> } catch {
> throw WrappedError(underlyingError: error)
> }
> }
> 
> ```

This one relies on a generalization of the rules that is (I believe) straightforward but hasn't been discussed so far. We want to say that `WrappedError<Never>` behaves like `Never` for typed throws purposes, so a function throwing `WrappedError<Never>` is not considered as throwing. Otherwise, this example does not mimic `rethrows` at all.

We could achieve that if we allowed _any_ uninhabited type to behave like `Never` in error position. In this case, `WrappedError<Never>` is uninhabited because it has a stored property of type `E`.

(The compiler today already has a variety of behaviors that are hardcoded on Never itself, and some on any uninhabited type. I believe most of the control-flow stuff is in the former category, while the type checker's implicit coercion of () -\> Never to () -\> T checks for the Never nominal type.)

---

<div class="post-metadata">

**Author:** ![Slava\_Pestov](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/slava_pestov/32/30204_2.png) [@Slava\_Pestov](https://forums.swift.org/u/Slava_Pestov)\
**Post date:** [October 3, 2023, 7:24pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/227 "2023-10-03T19:24:02Z")

</div>

> [@dmt](#):
>
> The second `f` accepts `g: () -> Void` which is actually `() throws(Never) -> Void`, and as `Never` is a concrete type it will be prioritized over the first `f`. Am I wrong?

I missed the fact that you had the other overload of `f`. Yes, if you overload `f` with throwing/non-throwing variants it would work, but I believe the stated goal of `rethrows` is precisely to avoid such overloads.

---

<div class="post-metadata">

**Author:** ![dmt](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/dmt/32/30830_2.png) [@dmt](https://forums.swift.org/u/dmt)\
**Post date:** [October 3, 2023, 7:26pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/228 "2023-10-03T19:26:28Z")

</div>

> [@Slava\_Pestov](#):
>
> This one relies on a generalization of the rules that is (I believe) straightforward but hasn't been discussed so far. We want to say that `WrappedError<Never>` behaves like `Never` for typed throws purposes, so a function throwing `WrappedError<Never>` is not considered as throwing. Otherwise, this example does not mimic `rethrows` at all.
> 
> We could achieve that if we allowed _any_ uninhabited type to behave like `Never` in error position. In this case, `WrappedError<Never>` is uninhabited because it has a stored property of type `E`.

Yes, exactly. I wrote about this in the previous thread as a reply to @Joe_Groff 's proposal of `Uninhabited` protocol and its inference.

> [@Status check: Typed throws](https://forums.swift.org/t/status-check-typed-throws/66637/87):
>
> Hm. struct S\<T\> { init(\_: T) {} } The struct should be inferred to be uninhabited when T: Uninhabited, but its layout tells us nothing about it. It's impossibility to construct a value of this type makes it uninhabited. But inits aren't always visible. So automatic conformance to Uninhabited could be challenging. It's certainly possible for enums and types with stored properties of uninhabited types. But in general case it seems a bit too much for an automated conformance. Another exampl…

---

<div class="post-metadata">

**Author:** ![dmt](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/dmt/32/30830_2.png) [@dmt](https://forums.swift.org/u/dmt)\
**Post date:** [October 3, 2023, 7:33pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/229 "2023-10-03T19:33:49Z")

</div>

> [@Slava\_Pestov](#):
>
> but I believe the stated goal of `rethrows` is precisely to avoid such overloads.

I agree. But on the other hand, if we build a relationship between errors it will be the same as "WrappedError" example. And I think if someone deliberately throws an error type independent from the source type, it's because the author don't want this relationship to exist. I don't think it's a good API design approach, but in general this should be possible in the language, and "two overloads solution" is good enough.

---

<div class="post-metadata">

**Author:** ![tcldr](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tcldr/32/24405_2.png) [@tcldr](https://forums.swift.org/u/tcldr)\
**Post date:** [October 3, 2023, 8:40pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/231 "2023-10-03T20:40:37Z")

</div>

> [@Slava\_Pestov](#):
>
> We could achieve that if we allowed _any_ uninhabited type to behave like `Never` in error position. In this case, `WrappedError<Never>` is uninhabited because it has a stored property of type `E`.

It's actually much simpler than I thought, with that added, it’s just as @dmt originally wrote it:

```swift
struct WrappedError<Wrapped: Error>: Error {
  let wrapped: Wrapped
}

func f<E>(g: () throws(E) -> Void) throws(WrappedError<E>) {
  do {
    try g()
  } catch {
    throw WrappedError(wrapped: error)
  }
}

```

That's tidy.

---

<div class="post-metadata">

**Author:** ![ksluder](https://avatars.discourse-cdn.com/v4/letter/k/eada6e/32.png) [@ksluder](https://forums.swift.org/u/ksluder)\
**Post date:** [October 3, 2023, 9:55pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/232 "2023-10-03T21:55:03Z")

</div>

> [@Slava\_Pestov](#):
>
> In this case, `WrappedError<Never>` is uninhabited because it has a stored property of type `E`.

How would this magic detection work if `WrappedError` were a type imported from a different, resilient module?

Also, how could `struct Result` be defined in a way that doesn’t cause `Result<_, Never>` to become uninhabited?

---

<div class="post-metadata">

**Author:** ![Joe\_Groff](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/joe_groff/32/1929_2.png) [@Joe\_Groff](https://forums.swift.org/u/Joe_Groff)\
**Post date:** [October 3, 2023, 10:03pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/233 "2023-10-03T22:03:54Z")

</div>

> [@ksluder](#):
>
> How would this magic detection work if `WrappedError` were a type imported from a different, resilient module?

Whether a type is uninhabited or not is derivable from its layout, so if it's frozen we can look at the layout. But more generally, if we had a layout constraint for it, then maybe you could explicitly declare `extension WrappedError: Uninhabited where Wrapped: Uninhabited {}` as an explicit guarantee that that's true (and have the compiler check that it is in fact true).

> [@](#):
>
> Also, how could `struct Result` be defined in a way that doesn’t cause `Result<_, Never>` to become uninhabited?

`Result` is an enum, and as long as an enum has one inhabited case, the enum is inhabited. When you hear functional programming nerds talk about structs and enums as "product types" and "sum types" this is one reason why: a struct has the product of all possible states of its fields, so if any field is uninhabited (zero) then zero \* n == zero. An enum has the sum of all possible states of its payloads, so if any field is uninhabited that doesn't affect the habitation of its other payloads, because zero + n == n. If you were going to write an `Uninhabited` declaration for `Result`, it would have to be both `where Success: Uninhabited, Failure: Uninhabited`.

---

<div class="post-metadata">

**Author:** ![Zollerboy1](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/zollerboy1/32/12997_2.png) [@Zollerboy1](https://forums.swift.org/u/Zollerboy1)\
**Post date:** [October 4, 2023, 5:02am UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/234 "2023-10-04T05:02:13Z")

</div>

> [@Jumhyn](#):
>
> I’m talking about the case where `SomeError` is a concrete type, i.e., you specifically _don’t_ want to make the higher-order function generic over the thrown error type.

So you want something with a behavior like this?

```swift
// hypothetical syntax:
typealias SomeErrorHelper<E> = if E == Never { Never } else { SomeError }

func f<E>(_ g: () throws(E) -> ()) throws(SomeErrorHelper<E>) { ... }

```

Maybe we should look into enabling very simple type logic to work like that. Not in this pitch of course but as a general direction it could be interesting.

---

<div class="post-metadata">

**Author:** ![wadetregaskis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wadetregaskis/32/8494_2.png) [@wadetregaskis](https://forums.swift.org/u/wadetregaskis)\
**Post date:** [October 4, 2023, 6:24pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/235 "2023-10-04T18:24:26Z")

</div>

### 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](https://github.com/apple/swift/blob/swift-5.5-RELEASE/docs/ErrorHandlingRationale.rst) 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](https://github.com/DougGregor/swift-typed-throws/blob/master/SE-XXX%20Typed%20throws.md#when-to-use-typed-throws) and [Effect on API resilience](https://github.com/DougGregor/swift-typed-throws/blob/master/SE-XXX%20Typed%20throws.md#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.

---

<div class="post-metadata">

**Author:** ![tcldr](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tcldr/32/24405_2.png) [@tcldr](https://forums.swift.org/u/tcldr)\
**Post date:** [October 5, 2023, 10:37am UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/236 "2023-10-05T10:37:37Z")

</div>

With regards to when and when not to use _typed throws_ there’s also a [long and detailed historical post](https://forums.swift.org/t/precise-error-typing-in-swift/52045) that drills down into specifics that largely arrives at the same conclusions reached in the proposal: in most cases it’s probably better to throw `any Error`, but, reluctantly, it’s needed for certain use cases.

I feel the current proposal does a good job of summarising the cases where it’s needed. i.e. generic expressivity, embedded/real-time applications. But maybe could benefit from examples where you _shouldn’t_ use typed throws. i.e. most places.

One particular use case that stood out for me as something that may benefit from highlighting as an anti-pattern was using typed throws to encode a makeshift call stack, i.e. creating a breadcrumb trail up the call-stack through the use of typed, nested errors.

Now, while this is a perfectly valid use case (having something akin to a stack trace to diagnose exactly where something might have gone awry is a useful thing) perhaps providing a lower friction community-blessed alternative (i.e. consistent across third party modules) would go someways to eliminating this potential anti-pattern.

How this would manifest, I don’t know. One option could be to include a Foundation Error type which _does_ include a `underlyingError: any Error?` property, or another could be something enabled by the runtime, maybe by some top-level annotation, which facilitates the accumulation of a stack trace equivalent up each of the ‘throw’ sites.

Something else that will help greatly with this is the approval of [SE-0409](https://forums.swift.org/t/accepted-with-modifications-se-0409-access-level-modifiers-on-import-declarations/67666): If a module imports a third-party module internally, they’ll be nudged when they accidentally ‘encode the dependency tree’ by including a third-party Error type as a part of their own public Error.

Now, all of this might sound a little prescriptive, but personally that’s something I appreciate in a language. It’s liberating to have one ‘blessed’ way of doing things rather than needing to learn dozens of equally clever alternatives. The smaller surface area is a boon to creativity.

---

<div class="post-metadata">

**Author:** ![davedelong](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/davedelong/32/38708_2.png) [@davedelong](https://forums.swift.org/u/davedelong)\
**Post date:** [October 5, 2023, 2:13pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/237 "2023-10-05T14:13:47Z")

</div>

The way I've been thinking about typed `throws` is a a bit different: typed errors are great (and probably preferred) _within_ a module. Like enums, they can aid you in enforcing exhaustivity and managing expectations within your own code. However also like enums, they're probably not the best when it comes to crossing a module boundary as `public` API. (Again, like enums, there _are_ cases where it's the right call, but have the same sort of constraints as using enums for public API: dealing with a [closed set](https://en.wikipedia.org/wiki/Closed_set) of possible values).

---

<div class="post-metadata">

**Author:** ![Jumhyn](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jumhyn/32/3891_2.png) [@Jumhyn](https://forums.swift.org/u/Jumhyn)\
**Post date:** [October 5, 2023, 3:10pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/238 "2023-10-05T15:10:48Z")

</div>

> [@wadetregaskis](#):
>
> 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 don't think this is necessarily contradictory, for a few reasons.

It's perfectly consistent to argue that it is more common for a throwing function to change the set of errors it wants to throw than it is for a function which initially has no error conditions to suddenly _start_ wanting to throw an error. I don't have the empirical data on hand to argue that case, but it seems like a reasonable position.

There's also an angle from the _use_ side—defaulting all functions to throwing would be an opinionated choice of the language that functions should assume they _may_ throw unless one affirmatively commits otherwise. I suspect this would have the effect of making many more functions throwing than we have today. This in turn would effect the set of solutions that are acceptable on the handling side of things. We'd either be imposing a very high annotation burden by asking users to mark every potentially-throwing call with `try`, or else end up with a regime where error handling are passthrough-by-default (after all, every function is throwing-by-default anyway).

I think both snippets you've quoted point to a single coherent conclusion: the zero-to-one step in number of thrown errors is more reasonably viewed as a difference in kind, whereas the one-to-N step is more reasonably viewed as a difference in degree. While this conclusion is certainly arguable, I don't think it's _inconsistent_.

---

<div class="post-metadata">

**Author:** ![JacksonUtsch](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jacksonutsch/32/18703_2.png) [@JacksonUtsch](https://forums.swift.org/u/JacksonUtsch)\
**Post date:** [October 6, 2023, 6:00pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/239 "2023-10-06T18:00:16Z")

</div>

Hence an open set or [non-exhaustive enums](https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute) would aid here for the use of a "dependency" or imported module/package/library. You'd maintain the benefits of typed throws. Swift comes across this case with C enums and you have to use the `@unknown default` case. You can currently cheat and create enums like this in C but they do not have associated values like Swift enums.

---

<div class="post-metadata">

**Author:** ![Philippe\_Hausler](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/philippe_hausler/32/22569_2.png) [@Philippe\_Hausler](https://forums.swift.org/u/Philippe_Hausler)\
**Post date:** [October 6, 2023, 8:31pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/240 "2023-10-06T20:31:09Z")

</div>

> [@tcldr](#):
>
> But maybe could benefit from examples where you _shouldn’t_ use typed throws.

From an API design standpoint: if you have a resilient layer for a public interface that is not inlined into the caller that can itself create some sort of failure that is a good layer to avoid concrete thrown types.

So if you have a function defined as:

```swift
public func frobulate() throws -> String 

```

If there is not `@inline(__always)` and/or it is not on a `@frozen` type (i.e. you want to be free to change the implementation) the fewer requirements on your implementation the better. With no typed throws it only remains that `frobulate` exists, that the signature is throwing and that the return type is a `String`. It could throw in 1.0 of a framework an `NSError`, then in 1.1 throw a `FrobulationFailure` and in 2.0 throw a `FrobulationFailure` or a `CancellationError`.

On the flip side; if you have the function completely visible for its implementation to the caller (to support inlining) then the signature is reasonable to consider if typed throws is appropriate. Likewise if the source of throwing is guaranteed to be known to exist from a given source; for example a closure that may have a distinct throwing type then it is also reasonable to make it have the mirrored throwing signature.

```swift
func withBruddle<E: Error>( _ apply: (Bruddle) throws(E) -> Void) throws(E) -> Void

```

There is a similarity in spirit to closed set enumerations versus open set enumerations. In the rethrows-like behavior of `withBruddle` it is a known closed set: the `with*` function can only throw what the apply can throw. The advantage here is that the information of what it does is not lost when traversing through an application of the throwing closure at the cost of enforcing strict behavior of what that `with*` function does.

Personal speculation: ABI/API stable APIs won't use typed throws for root level sources of failure, non-ABI/API stable APIs may use them. Furthermore, constrained environments that have discrete known failure modes and are sensitive to the existential costs might also consider typed throws at the root of actions. Things that rethrow (either by protocol conformances, or by closures) will likely re-emit those failures where possible. Likewise, I would expect that APIs that violate that expectation (throwing from something that ought to consider a source of rethrowing) might need some refinement.

In short I think that typed throws will be exceedingly useful in certain scenarios, but it will be something that will have to be considered carefully when designing for ABI/API stability. And perhaps those discussions are something that should be had when designing things; who uses the error type and how?

---

<div class="post-metadata">

**Author:** ![wadetregaskis](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/wadetregaskis/32/8494_2.png) [@wadetregaskis](https://forums.swift.org/u/wadetregaskis)\
**Post date:** [October 6, 2023, 9:19pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/241 "2023-10-06T21:19:20Z")

</div>

> [@Philippe\_Hausler](#):
>
> In short I think that typed throws will be exceedingly useful in certain scenarios, but it will be something that will have to be considered carefully when designing for ABI/API stability. And perhaps **those discussions are something that should be had when designing things; who uses the error type and how**?

Precisely.

Good conclusion, but odd start: 🙂

> [@Philippe\_Hausler](#):
>
> If there is not `@inline(__always)` and/or it is not on a `@frozen` type (i.e. you want to be free to change the implementation) the fewer requirements on your implementation the better.

Better for whom? Certainly not the _users_ of this API.

Every aspect of API design is trade-offs between forward-compatibility and specificity. There's nothing special about errors in this regard.

Each case should use whatever compromise suits it best, whether that be a frozen enum, an open enum, a struct, a protocol (and then either opaquely or existentially), etc. Same as for its non-error return type, its parameters (e.g. take `String`s specifically or genericise over `StringProtocol`? `Int` or `FixedWidthInteger`, or `BinaryInteger`?), and whether it returns an optional or throws at all (maybe it doesn't now, but might want to in future?).

Not to mention the structural elements - should you use structs (and if so expose them directly, or opaquely?) or should you use classes? Enums or mimicry thereof with static member variables? And so on. Structs are directly exposed _all the time_ in so-called stable APIs, and it works fine, because _not everything is likely to change_.

And "breaking" compatibility is not the bogeyman some make it out to be, anyway. Apple's "stable" APIs "break" all the time, intentionally (mostly). Functionality changes (generally improving), bugs are fixed, etc. Sometimes that's done within the existing API, sometimes it requires new overloads, sometimes new APIs entirely. Users adapt and progress and as long as the "breaks" have justification, everything moves forward with at worst a little griping (and then, mainly of the "ugh, reality is annoying", not "this was a bad _choice_").

---

<div class="post-metadata">

**Author:** ![Jon\_Shier](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jon_shier/32/921_2.png) [@Jon\_Shier](https://forums.swift.org/u/Jon_Shier)\
**Post date:** [October 6, 2023, 9:51pm UTC](https://forums.swift.org/t/pitch-n-1-typed-throws/67496/242 "2023-10-06T21:51:45Z")

</div>

Aside from the new ABI impact of having an error type in the function definition, it doesn't seem like there are any new considerations typed throws requires beyond what having a public error type in the first place already does. In fact, starting with typed throws seems superior in pretty much all cases to me.

- With a public error type you already have to deal with breaking exhaustiveness when using an enum or users missing cases when using a struct.
- With compiler-checked error types, if you use an API below your public surface that produces untyped errors, the compiler will tell you about it, requiring you to either untype your error or handle that fact in your typed error. Otherwise this is just a silent change you may miss, on both sides.
- Developers can easily erase the public error, either automatically or by overloading.

Really the only thing the language needs is (the equivalent of) `@frozen` enums in non-stable modes and the API evolution concerns would be completely solved. In that case, even if you did change your underlying implementation to produce a new error _and_ you didn't already have a fallback case in your typed error, you'd be able to easily add cases to your existing error.

In particular, I'm not sure this is really true:

> [@Philippe\_Hausler](#):
>
> It could throw in 1.0 of a framework an `NSError`, then in 1.1 throw a `FrobulationFailure` and in 2.0 throw a `FrobulationFailure` or a `CancellationError`.

Changing the underlying type of an error is a major API breaking change, as users of 1.0 who updated to 1.1 would lose any error handling they had for `NSError`. Currently Swift can't surface these changes in the language, but they're still breaking at runtime, which is why defaulting to typed errors would be the better start. At best the consuming devs would have an "unknown error" case to handle casting failures and `catch`, but that assumes a lot from them and generally means a degraded error handling experience anyway.

[Previous page](https://forums.swift.org/t/pitch-n-1-typed-throws/67496.md?page=11)

[Next page](https://forums.swift.org/t/pitch-n-1-typed-throws/67496.md?page=13)
