Handle Errors Using Throwing Functions in Swift
Introduction
A key question when writing programs is how to handle something going wrong. Optional types provide one excellent strategy for handling these cases. However, optionals cannot distinguish between different types of problems. Take the following example of a Product
structure:
struct Product {let name: Stringlet price: Double?}
A Product
has two properties: a name
and a price
. Sometimes, the price might be nil
because it hasn’t been entered yet. A store wants to know the average price of a Product
and the following function provides that answer:
func averagePrice(of products: [Product]) -> Double? {guard !products.isEmpty else { return nil }var sum = 0.0for product in products {guard let price = product.price else { return nil }sum += price}return sum / Double(products.count)}
The averagePrice(of:)
method returns nil
when either the array of products is empty or at least one of the prices is nil
. Once the function returns, there’s no way of telling which of those was the case without going back to the original products
array.
Throwing functions give us a way to write functions that can be more descriptive about what went wrong. Let’s take a look at their syntax.
Defining a throwing function
Throwing functions are marked with the throws
keyword after the function name and before the return arrow ->
:
func averagePrice(of products: [Product]) throws -> Double {// ...}
Throwing Errors
Functions that have been marked as throws
can throw an Error
in their implementation. Error
is a Swift protocol with no requirements. Because of this, any type can be marked as conforming to Error
. Typically, you should use an enumeration to represent all of the possible errors:
enum AverageCalculationError: Error {case noProductscase productWithNoPrice}
In the enumeration above, the errors that can happen when calling averagePrice(of:)
are listed explicitly. The function can be rewritten as a throwing function:
func averagePrice(of products: [Product]) throws -> Double {guard !products.isEmpty else {throw AverageCalculationError.noProducts}var sum = 0.0for product in products {guard let price = product.price else {throw AverageCalculationError.productWithNoPrice}sum += price}return sum / Double(products.count)}
It’s immediately clear what the error is. Additionally, the function no longer returns an optional Double
. Either it calculates the average price or it throws an error that specifies what the problem was. Note that because throwing an error stops the execution of the rest of the function, throw
can be used in the else
block of a guard
statement.
The try
keyword
Calling with try!
Calling a throwing function could result in an error being thrown. While that gives great information about what went wrong, we need to tell the compiler what to do with the error. Because the call could fail, Swift requires that you use the keyword try
before invoking a throwing function.
Using try!
means that if an error is thrown, the whole program should crash:
let clothingStore = [Product(name: "Coat", price: nil),Product(name: "Shoes", price: nil),Product(name: "Hat", price: nil)]let averageClothingPrice = try! averagePrice(of: clothingStore)// Fatal error: 'try!' expression unexpectedly raised an error: AverageCalculationError.productWithNoPrice:
The do-catch
block
Using try!
is like force unwrapping an optional, and should be used only when you are sure that no errors will be thrown. A better way to handle errors is using a do-catch
statement. A do-catch
statement has the following syntax:
do {// Call throwing functions}catch {// Handle errors}
Inside of the do
block, you can use the try
keyword (no punctuation) to call throwing functions. Any errors that occur will be handled in the catch
block.
let clothingStore = [Product(name: "Coat", price: nil),Product(name: "Shoes", price: nil),Product(name: "Hat", price: nil)]do {let averageClothingPrice = try averagePrice(of: clothingStore)print("The average price is \(averageClothingPrice)")}catch {print("An error occurred: \(error)")}// Prints: An error occurred: productWithNoPrice
Because the averagePrice(of:)
method throws an error, it is handled in the catch
block. Note that there is a variable named error
that you have access to in the catch
block. This is the error thrown by the function.
This structure also allows for explicit handling of different errors:
let emptyStore = [Product]()do {let average = try averagePrice(of: emptyStore)print("The average price is \(average)")}catch AverageCalculationError.noProducts {print("Error: There are no products in the store")}catch AverageCalculationError.productWithNoPrice {print("Error: There is at least one product without a price")}// Prints: Error: There are no products in the store
Calling with try?
If you’d like to call a throwing function safely but don’t need to handle the errors independently, you can use try?
. Using try?
means that if the call fails, it simply becomes nil
.
let officeStore = [Product(name: "Pen", price: 0.50),Product(name: "Stapler", price: 5.00),Product(name: "Paperclips", price: 2.50)]if let average = try? averagePrice(of: officeStore) {print("The average price is \(average)")}
If the individual errors from a throwing function aren’t important, try?
is a good way to safely handle errors.
Review
Throwing functions are a great tool for adding more information about errors. By using do-catch
blocks, and the try
, throw
, and throws
keywords, you can now write and call your own throwing functions. As you work with more Swift APIs, you’ll see more methods that throw errors and you are now equipped for handling them.
Author
'The Codecademy Team, composed of experienced educators and tech experts, is dedicated to making tech skills accessible to all. We empower learners worldwide with expert-reviewed content that develops and enhances the technical skills needed to advance and succeed in their careers.'
Meet the full teamRelated articles
- Article
Thinking About Errors in Your Code Differently
This article explains how errors in your code aren't a bad thing, but rather an opportunity to learn. Additionally, it provides several steps to identify and fix these errors. - Article
Errors in C++
Errors are simply unavoidable when you develop a program, let's learn more about them!
Learn more on Codecademy
- Skill path
Build an Intermediate iOS App with SwiftUI
Learn how to use Swift and SwiftUI to build more complex iOS apps that can store present data.Includes 4 CoursesWith CertificateIntermediate8 hours - Career path
iOS Developer
Learn how to use Swift and SwiftUI to build iOS applications.Includes 26 CoursesWith CertificateBeginner Friendly35 hours