Rethrows in Swift allow to forward an error thrown by a certain parameter function. It is used a lot in methods like
map
,filter
andforEach
help the compiler determine if atry
prefix is needed.
1. How to use rethrows keyword
The rethrows keyword used in functions does not throw an error but instead forwards the error from their parameter functions. It also allows the compiler to request the try
keyword only if a given callback is actually causing the error.
Take the following example of a rethrowing method that does a rethrowing callback:
1 2 3 4 | func rethrowingFunction(throwingCallback: () throws -> Void) rethrows { try throwingCallback() } |
If the callback we passed did not cause an error, we could call the method like this:
1 2 3 4 | rethrowingFunction { print("I'm not throwing errors") } |
However, as soon as our callback is likely to cause an error, the compiler asks us to use try
for our rethrowing method:
The compiler pointed out that our rethrows method is not marked with try
This is great because it allows us to only use the try
keyword if the body actually gets an error. There is no need to wrap our method in a try-catch
if it is not likely to receive an error.
If we were to write the same method without rethrows
, we would have to use try
in all cases:
1 2 3 4 5 6 7 8 | 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 parameter function is likely to cause an error.
2. An example in real case
Now that we know how rethrows
keyword rethrows
, we can see an example in a real case. In the following code, we created a closure method for an array of strings in which we concatenated elements based on a predicate:
1 2 3 4 5 6 7 | extension Array where Self.Element == String { func joined(separator: String, where predicate: (Element) throws -> Bool) rethrows { try filter(predicate) .joined(separator: separator) } } |
By default, the standard filter
method is rethrow the errors. However, if we want to benefit from this behavior in our joined
closure method, we also need to implement our custom rethrowing method. Otherwise, we will not be able to throw any errors in our predicate.
A usage example might look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | enum Error: Swift.Error { case numbersNotAllowed } var names = ["Light", "Near", "Eval", "Misa4"] 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` } |
Since we have a name with the number 4 in it, the combined method will cause an error.
3. Use rethrows to cover the error
Another common use case is to encapsulate other errors into a locally defined error type. In the following example, we have specified a storage controller that returns a Result enum with a type StorageError strong. However, our FileManager method can throw any other error we want to wrap in the StorageError.otherError
instance.
Using the perform rethrowing method with the given callback, we can catch any error that happened:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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) } } } |
4. Override the methods with the rethrows keyword
It is important to understand that throwing methods cannot be used to override rethrowing method because it will suddenly turn throwable method into throwing method. The throwing method also cannot respond to the “protocol” protocol request to the rethrowing method for the same reason.
On the other hand, a rethrowing method can override a throwing method because in the end a throwing method is also a throw function. This also means you can use the rethrowing method to respond to protocol requests for throwing methods.
5. Conclusion
Rethrows in Swift prevents the use of the try
keyword for no reason. If the inner method does not cause an error, then the rethrows keyword ensures the compiler knows that no try
is needed. A rethrowing method will forward any errors generated by its function parameters.
Hopefully, this will help you with more efficient coding.
So my post here is over. Thank you for watching the article.