VOOZH about

URL: https://www.codecademy.com/article/handle-errors-using-throwing-functions-swift

⇱ Handle Errors Using Throwing Functions in Swift | Codecademy


Skip to Content
Articles

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]:

structProduct{
let name:String
let price:Double?
}
Copy to clipboard
Copy to clipboard

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:

funcaveragePrice(of products:[Product])->Double?{
guard!products.isEmpty else{returnnil}
var sum =0.0
for product in products {
guardlet price = product.price else{returnnil}
sum += price
}
return sum /Double(products.count)
}
Copy to clipboard
Copy to clipboard

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 ->:

funcaveragePrice(of products:[Product])throws->Double{
// ...
}
Copy to clipboard
Copy to clipboard

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:

enumAverageCalculationError:Error{
case noProducts
case productWithNoPrice
}
Copy to clipboard
Copy to clipboard

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:

funcaveragePrice(of products:[Product])throws->Double{
guard!products.isEmpty else{
throwAverageCalculationError.noProducts
}
var sum =0.0
for product in products {
guardlet price = product.price else{
throwAverageCalculationError.productWithNoPrice
}
sum += price
}
return sum /Double(products.count)
}
Copy to clipboard
Copy to clipboard

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:
Copy to clipboard
Copy to clipboard

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
}
Copy to clipboard
Copy to clipboard

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 =tryaveragePrice(of: clothingStore)
print("The average price is \(averageClothingPrice)")
}
catch{
print("An error occurred: \(error)")
}
// Prints: An error occurred: productWithNoPrice
Copy to clipboard
Copy to clipboard

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 =tryaveragePrice(of: emptyStore)
print("The average price is \(average)")
}
catchAverageCalculationError.noProducts {
print("Error: There are no products in the store")
}
catchAverageCalculationError.productWithNoPrice {
print("Error: There is at least one product without a price")
}
// Prints: Error: There are no products in the store
Copy to clipboard
Copy to clipboard

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)
]
iflet average =try?averagePrice(of: officeStore){
print("The average price is \(average)")
}
Copy to clipboard
Copy to clipboard

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.

Codecademy Team

'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 team

Learn more on Codecademy