Typed Throws

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Open the Lesson02-Typed-Throws playground. Start on the Lesson02-typed-throw-a page.

Swift 6.0 introduced typed throws, which allow you to specify the type of Error that a function can throw. Modify open(_:) to throw only BakeryError:

func open(_ shouldOpen: Bool = Bool.random()) throws(BakeryError) -> Bool {  // 1
  guard shouldOpen else {
    throw Bool.random() ? .inventory : .noPower  // 2
  }
  return shouldOpen
}
  1. You tell the compiler that open(_:) throws only BakeryErrors.
  2. You don’t need to specify BakeryError when throwing the error.

The compiler won’t let you throw any other type of Error.

Now, do the same for orderPastry(item:amountRequested:flavor:):

func orderPastry(item: String, amountRequested: Int, flavor: String) throws(BakeryError) -> Int {  // 1
  guard let pastry = itemsForSale[item] else {
    throw .noSuchItem  // 2
  }
  guard flavor == pastry.flavor else {
    throw .wrongFlavor
  }
  guard amountRequested <= pastry.numberOnHand else {
    throw .tooFew(numberOnHand: pastry.numberOnHand)
  }
  pastry.numberOnHand -= amountRequested
      
  return pastry.numberOnHand
}

Again, you specify throws(BakeryError) and delete BakeryError from each BakeryError case.

And, when catching errors thrown by these two methods, remove explicit mentions of BakeryError:

do {
  try bakery.open()
  try bakery.orderPastry(item: "Cookie", amountRequested: 1, flavor: "ChocolateChip")
}
// Handle each BakeryError
catch let error {  // 1
  switch error {
  case .inventory, .noPower:  // 2
    print("Sorry, the bakery is now closed.")
  case .doNotSell:
    print("Sorry, but we don't sell this item.")
  case .wrongFlavor:
    print("Sorry, but we don't carry this flavor.")
  case .tooFew(numberOnHand: let items):
    print("We only have \(items) cookies left.")
  }
}

//catch {  // 3
//  print("Something went wrong: \(error)")
//}
  1. You don’t need to cast error as BakeryError — option-click it to see it’s already of type BakeryError.
  2. You don’t need to specify BakeryError for its cases.
  3. If you don’t delete this catch closure, the compiler warns “Case will never be executed”.

Backwards Compatibility

You can still use throws without specifying an Error type — the compiler translates it to throws(any Error). And, it converts functions that don’t throw an error to throws(Never).

func couldThrow() throws { ... }
// compiler translates this to:
func couldThrow() throws(any Error) { ... }

func neverThrows() { ... }
// compiler translates this to:
func neverThrows() throws(Never) { ... }

Pain Point: At Most One Type

Using typed throws, you can specify at most one type of Error. What if you want your function to throw more than one type of Error?

enum NetworkError: Error {
  case unexpected
  case disconnected
  case timeout(seconds: Int)
  case invalidURL(_ url: URL)
  case httpError(statusCode: Int)
}

enum AuthError: Error {
  case missingToken
  case tokenExpired
}
func loadData() throws(NetworkError, AuthError) {  // compiler error: consecutive statements
  // networking code that can throw NetworkError
  // authentication code that can throw AuthError
}
func loadData() throws {
  // networking code can throw NetworkError
  // authentication code can throw AuthError
}
do {
  try loadData()
}
catch let authError as AuthError {
  print("auth error", authError)
  switch authError {
  case .missingToken:
    print("missing token")
    // present a login screen
  case .tokenExpired:
    print("token expired")
    // attempt a token refresh
  }
}
catch let networkError as NetworkError {
  print("network error", networkError)
  // present alert explaining what went wrong
}
catch {
  print("error", error)
}
enum FeedError: Error {
  case authError(AuthError)
  case networkError(NetworkError)
  // include 'other' if you need flexibility
  // case other(any Error)
}
func loadData2() throws(FeedError) {
  // networking code can throw NetworkError
  // authentication code can throw AuthError
}

do {
  try loadData2()
}
catch {
  switch error {
  case .authError(let authError):
    // handle auth error
  case .networkError(let networkError):
    // handle network error
  }
  // no other error type is possible
}
func cacheFeed() throws { }

// Two methods might throw different error types so the compiler must drop down to any Error
// since both methods throw something that conforms to Error
do {
  try loadData2()
  try cacheFeed()
} catch {
  // error is any Error here
}
See forum comments
Download course materials from Github
Previous: Introduction Next: Conclusion