Give your simulator superpowers

RocketSim: An Essential Developer Tool
as recommended by Apple

How to use the rethrows keyword in Swift

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:

The compiler indicates that our rethrows method isn't marked with 'try'
The compiler indicates that our rethrows method isn’t marked with ‘try’

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!

 

Stay updated with the latest in Swift

The 2nd largest newsletter in the Apple development community with 18,327 developers.


Featured SwiftLee Jobs

Find your next Swift career step at world-class companies with impressive apps by joining the SwiftLee Talent Collective. I'll match engineers in my collective with exciting app development companies. SwiftLee Jobs