Using Fatal Errors to Add Clarity and Elegance to Swift

A few months ago, I stumbled upon a discussion in the thoughtbot guides about the use of fatal errors in Swift. It seems every developer has an opinion about fatal errors and a consensus hasn't been reached yet in the Swift community. The only mention in The Swift Programming Language is in the section that discusses the guard statement and early exit.

When I fist encountered the fatalError(_:file:line:) function, it reminded me of the abort() function. Even though both functions cause the immediate termination of your application, the fatalError(_:file:line:) function is different and, in the context of Swift, it is much more useful.

What to Do When You Don't Know What to Do

Ever since I started working with Swift, I have been struggling with the implementation of the tableView(_:cellForRowAt:) method. If you think that sounds silly, then take a look at the following example.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.reuseIdentifier, for: indexPath) as? SettingsTableViewCell {
        // Configure Cell
        cell.textLabel?.text = "Some Setting"

        return cell

    } else {
        return UITableViewCell()
    }
}

There are several variations of the above implementation and I have tried all of them. The idea is simple. We expect an instance of the SettingsTableViewCell class if we ask the table view for a cell with the reuse identifier of the SettingsTableViewCell class. Because the dequeueReusableCell(withIdentifier:for:) method returns a UITableViewCell instance, we need to cast the result to an instance of the SettingsTableViewCell class.

This is inconvenient since we always expect to receive a SettingsTableViewCell instance if we ask the table view for a cell with the reuse identifier of the SettingsTableViewCell class. We could use as! instead of as?, but that is not a solution I feel comfortable with. I avoid the exclamation mark whenever I can.

If, for some reason, something goes wrong, we return a UITableViewCell instance from the tableView(_:cellForRowAt:) method. But that should never happen. Right?

While this is fine and necessary to make sure we return a UITableViewCell instance from the tableView(_:cellForRowAt:) method, I hope you can see that we are implementing a workaround for a scenario we do not expect, a scenario that should never occur.

Guard Against Unexpected Events

Every time I implement tableView(_:cellForRowAt:) I wonder if there is a better approach. And there is. We need to guard against the event that the table view hands us a UITableViewCell instance we don't expect and, if that happens, we throw a fatal error.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.reuseIdentifier, for: indexPath) as? SettingsTableViewCell else { fatalError("Unexpected Index Path") }

    // Configure Cell
    ...

    return cell
}

The application crashes if a fatal error is thrown. Why is this better? This is a better solution for two reasons.

Unexpected State

If the application runs into a scenario in which a fatal error is thrown, we communicate to the runtime that the application is in a state it does not know how to handle.

In the first example, the solution was to return a UITableViewCell instance. Does that solve the problem? No. The application ignores the situation and avoids causing havoc by returning a UITableViewCell instance. The application avoids dealing with the unexpected state it is in.

Finding and Fixing the Bug

If the application runs into a state we did not anticipate, it means a bug has slipped into the codebase. If the application is terminated due to a fatal error being thrown, we have work to do. It means we need to find the bug and fix it.

The above solution, using a guard statement and throwing a fatal error, is a solution I am very happy with. It avoids the obsolete if statement of the first implementation of the tableView(_:cellForRowAt:) method and it correctly handles the situation. The result is a much more elegant implementation of the tableView(_:cellForRowAt:) method. And it adds clarity to the implementation of the tableView(_:cellForRowAt:) method.

Use Fatal Errors Sparingly

This doesn't mean that you need to use fatal errors whenever you want to avoid error handling or the application enters a state that is hard to recover from. I use fatal errors only when the application can enter in a state it was not designed for. Take a look at the following example.

import Foundation

enum Section: Int {

    case news
    case profile
    case settings

    var title: String {
        switch self {
        case .news: return NSLocalizedString("section_news", comment: "news")
        case .profile: return NSLocalizedString("section_profile", comment: "profile")
        case .settings: return NSLocalizedString("section_settings", comment: "settings")
        }
    }

}

struct SettingsViewViewModel {

    func title(for section: Int) -> String {
        guard let section = Section(rawValue: section) else { fatalError("Unexpected Section") }
        return section.title
    }

}

The title(for:) method of the SettingsViewViewModel struct does not expect a value it cannot use to instantiate a valid Section instance with. If the value of the section parameter is invalid, it would not know what to do. In that scenario, a fatal error is thrown.

If the application does enter that scenario, it means you made a logical mistake and it is your task to find out why and how it can be resolved.

Clarity and Elegance

The use of the fatalError(_:file:line:) function has made my code more readable without compromising the inherent safety of the Swift language. It adds clarity to the code I write and Swift regains its elegance. Give it a try and let me know if you like it.

Stop Writing Swift That Sucks

Download the Swift Patterns I Swear By

About Bart Jacobs

About bart jacobs

My name is Bart Jacobs and I run a mobile development company, Code Foundry. I've been programming for more than fifteen years, focusing on Cocoa development soon after the introduction of the iPhone in 2007.

Stop Writing Swift That Sucks

In my free book, you learn the four patterns I use in every Swift project I work on. You learn how easy it is to integrate these patterns in any Swift project.