Give your simulator superpowers

RocketSim: An Essential Developer Tool
as recommended by Apple

Typed throws in Swift explained with code examples

Typed throws are new since Xcode 16 and allow you to define the type of error a method throws. Instead of handling any error, you can handle exact cases and benefit from compiling time checks for newly added instances. They were introduced and designed in Swift Evolution proposal SE-413.

I encourage you to read Try Catch Throw: Error Handling in Swift with Code Examples before diving into typed errors so you’re fully aware of the basics of error handling in Swift. This article will continue on the same path using similar code examples.

Specifying the Error Type

Imagine having the following username validator code that already fails with a consistent type of error:

struct UsernameValidator {
    enum ValidationError: Error {
        case emptyName
        case nameTooShort(nameLength: Int)
    }
    
    static func validate(name: String) throws {
        guard !name.isEmpty else {
            throw ValidationError.emptyName
        }
        guard name.count > 2 else {
            throw ValidationError.nameTooShort(nameLength: name.count)
        }
    }
}

In all cases of failure, the type will be ValidationError. This allows us to use typed throws inside the method definition:

/// Using throws(ValidationError) we specify the error type to always be `ValidationError`
static func validate(name: String) throws(ValidationError) {
    guard !name.isEmpty else {
        throw ValidationError.emptyName
    }
    guard name.count > 2 else {
        throw ValidationError.nameTooShort(nameLength: name.count)
    }
}

By defining the expected outcome error, we benefit from compile-time checks and auto-completion. We could rewrite the above method without using the ValidationError type definition inline:

static func validate(name: String) throws(ValidationError) {
    guard !name.isEmpty else {
        throw .emptyName
    }
    guard name.count > 2 else {
        throw .nameTooShort(nameLength: name.count)
    }
}

Autocompletion is smart enough to detect the type of error and helps you accordingly:

You can benefit from autocompletion when using typed throws.
You can benefit from autocompletion when using typed throws.

Finally, the compiler will check and throw an error if you’re trying to throw a different kind of error:

The compiler will throw an error if you're trying to throw a different type of error.
The compiler will throw an error if you’re trying to fail with a different error.

Since we’re trying to throw OtherError instead of ValidationError, we’re running into the following error:

Thrown expression type ‘UsernameValidator.OtherError’ cannot be converted to error type ‘UsernameValidator.ValidationError’

Compile-time checks are highly valuable and will also occur when handling errors inside a do-catch clause.

Stay updated with the latest in Swift

The 2nd largest newsletter in the Apple development community with 19,429 developers. Don't miss out – Join today:

You can always unsubscribe, no hard feelings.

Handling typed throws inside a do-catch clause

The introduction of typed throws also added new functionality for do-catch clauses. You can explicitly define the expected error as follows:

do throws(UsernameValidator.ValidationError) {
    try UsernameValidator.validate(name: name)
} catch {
    switch error {
    case .emptyName:
        print("You've submitted an empty name!")
    case .nameTooShort(let nameLength):
        print("The submitted name is too short!")
    }
}

This allows you to switch on the thrown error and handle all cases. If any new cases get added in the future, you’ll be notified by the compiler with a “Switch must be exhaustive” compile-time failure.

Specifying the error type inside a do-catch clause is only valuable in case you want to prevent the same do-closure from throwing other errors:

Specifying typed throws inside do-catch clauses enables useful compile-time checks.
Specifying typed throws inside do-catch clauses enables useful compile-time checks.

Swift is smart enough to inherit typed throws from methods inside the do-clause. In other words, we could write the above method as follows and still benefit from autocompletion:

do {
    try UsernameValidator.validate(name: name)
} catch {
    switch error {
    case .emptyName:
        print("You've submitted an empty name!")
    case .nameTooShort(let nameLength):
        print("The submitted name is too short!")
    }
}

Conclusion

Typed throws are a valuable addition to Swift, allowing us to write more predictable code. SDKs, in particular, can benefit from this feature by better predicting the error to expect. More compile-time checks help us avoid forgetting about handling any new error cases in the future.

If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.

Thanks!