Does anyone know what the actual intent of rethrows was at its genesis? It'd be interesting to know if that intent is what we see today in its implementation or if it followed a different path.
What rethrows
actually means today
It's apparent that there's multiple, conflicting interpretations of its purpose today. However, all but one are demonstrably incorrect insofar as how the compiler actually behaves today. Specifically, we have:
-
Rethrows means the function doesn't throw itself, it only passes up errors thrown from its closure arguments.
This is incorrect - you can catch the errors and throw different errors.
-
Rethrows means the function will throw if & when one of its closure arguments does, but in no other case.
This is incorrect - you can catch the errors and return without throwing.
-
Rethrows means the function may only throw errors of the same type as its closure parameter(s) throw.
This is incorrect - there are no restrictions on the types of any of the errors. You also may not throw at an arbitrary time, even if it's of the same error type as one of the closure arguments'.
-
Rethrows means the function may throw only if & only when one of its closure arguments does.
This is
rethrows
currently. This is also not just a matter of runtime behaviour but also compile-time typing - if the closures don't throw at all, the function doesn't throw at all (i.e. notry
is needed to call it, no error-handling code is emitted, etc).
It's important to note that rethrows
is perhaps not the ideal name for this functionality. But naming is hard, and while only-throws-if-and-when-a-closure-argument-throws
is clearer, it's a little wordy.
Typed throws partially obsolete rethrows
Not that rethrows
behaves that way today anyway, but one doesn't need rethrows for that purpose when typed throws are available, because that type constraint can be expressed using normal generics grammar (in the same manner as for tying parameter types to non-error return types), e.g.:
func foo<E: Error>(_ arg: () throws(E) -> ()) throws(E) { … }
This also handles one aspect of what rethrows
actually does today which is to allow a single function definition to produce both throwing and non-throwing variants (via the use of Never
as the error type).
So for those purposes rethrows
was never applicable and is no longer necessary, respectively.
The unique purpose of rethrows
However, typed throws do not have any way to express the other aspect of what rethrows
means, which is the restriction on when an exception may be thrown at runtime.
So your question is really: is that specific functionality important?
I'm not certain what the answer is yet, and there may also be different answers depending on whether you mean important to the compiler or to humans.
However, I suspect there's a solid case for the latter audience, at least. Rethrows seems like an important hint that a function has no internal error cases, only whatever the closures bring in. Maybe the compiler doesn't care, but that's helpful to a human in interpreting and understanding what the function does and how it behaves.
If it's ultimately decided that such functionality is not still important, then indeed rethrows
could be removed (hopefully with suitable migrators and FixIts etc to convert existing code over).
If that functionality is still important, then rethrows
still has its place but doesn't have to accept a type argument, since in principle the rethrows
annotation has only that specific meaning and is orthogonal to throws
- you can optionally add a separate throws(X)
annotation if you wish to impose any type constraints. The two could be combined for convenience - rethrows(X)
- but arguably that conflates their purposes and may cause confusion…?
(it might also be worth considering a new, better name for rethrows
, although nothing immediately comes to mind)