Rethrows in Swift allows forwarding a thrown error by a given function parameter. It’s used a lot in methods like map
, filter
, and forEach
and helps the compiler to determine whether or not a try
prefix is needed.
In my experience, you don’t have to write rethrowing methods that often. However, once you know how it works, you start to see more cases in which a rethrowing method could make sense.
How to use the rethrows keyword
The rethrows keyword is used in functions that do not throw errors themselves but instead forward errors from their function parameters. It also allows the compiler to ask for the try
keyword only if the given callback actually is throwing errors.
Take the following example of a rethrowing method taking a throwing callback:
func rethrowingFunction(throwingCallback: () throws -> Void) rethrows {
try throwingCallback()
}
If the callback we pass in doesn’t throw an error, we can call the method as follows:
rethrowingFunction {
print("I'm not throwing errors")
}
However, as soon as our callback is potentially throwing an error, the compiler requires us to use try
for our rethrowing method:
This is great as it allows us to only use the try
keyword if the body is actually throwing an error. There’s no need to wrap our method in a try-catch if there isn’t a possibility of receiving an error.
If we were to write the same method without rethrows
, we would end up having to use try
in all cases:
func rethrowingFunction(throwingCallback: () throws -> Void) throws {
try throwingCallback()
}
try rethrowingFunction {
print("I'm not throwing errors")
}
In other words, rethrowing methods only need to be marked with try if their function parameter is potentially throwing an error.
A real case example
Now that we know how the rethrows keyword works, we can look at a real-case example. In the following code, we’ve created a wrapper method for an array of strings in which we join the elements based on a predicate:
extension Array where Self.Element == String {
func joined(separator: String, where predicate: (Element) throws -> Bool) rethrows {
try filter(predicate)
.joined(separator: separator)
}
}
The standard filter
method is rethrowing errors by default. However, if we want to benefit from this behavior in our wrapping joined
method we need to make our custom method rethrowing as well. Otherwise, we would not be able to throw any error in our predicate.
An example usage could look as follows:
enum Error: Swift.Error {
case numbersNotAllowed
}
var names = ["Antoine", "Maaike", "Bernie", "Angi3"]
do {
try names.joined(separator: ", ", where: { name -> Bool in
guard name.rangeOfCharacter(from: .decimalDigits) == nil else {
throw Error.numbersNotAllowed
}
return true
})
} catch {
print(error) // Prints: `numbersNotAllowed`
}
As we have a name with a number 3 in it, the joined method will throw an error.
Using rethrows to wrap errors
Another common use case is to wrap other errors into a locally defined error type. In the following example, we’ve defined a storage controller which returns a Result enum with a strongly typed StorageError. However, our FileManager method can throw any other error which we want to wrap in our StorageError.otherError
case.
Using the rethrowing perform
method with the given callback we allow ourselves to catch any occurred errors:
struct StorageController {
enum StorageError: Swift.Error {
case fileDoesNotExist
case otherError(error: Swift.Error)
}
let destinationURL: URL
func store(_ url: URL, completion: (Result<URL, StorageError>) -> Void) throws {
guard FileManager.default.fileExists(atPath: url.path) else {
completion(.failure(StorageError.fileDoesNotExist))
return
}
try perform {
try FileManager.default.moveItem(at: url, to: destinationURL)
completion(.success(destinationURL))
}
}
private func perform(_ callback: () throws -> Void) rethrows {
do {
try callback()
} catch {
throw StorageError.otherError(error: error)
}
}
}
Overriding methods with the rethrows keyword
It’s important to understand that a throwing method can’t be used to override a rethrowing method as it would suddenly turn a ‘might throw method into a ‘throwing’ method. A throwing method also can’t satisfy a protocol requirement for a rethrowing method for the same reason.
A rethrowing method, on the other side, can override a throwing method as in the end, a throwing method is also a ‘might throw function. This also means you can use a rethrowing method to satisfy a protocol requirement for a throwing method.
Conclusion
Rethrows in Swift can be great to prevent using the try
keyword for no reason. If the inner method isn’t throwing an error, the rethrows keyword makes sure the compiler knows that a try
isn’t required. A rethrowing method forwards any errors thrown by its function parameters.
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!