That protocol has exactly zero user-facing requirements (technically, there are error domain and error code requirements for Darwin platforms, but they're hidden, so for all intents and purposes they don't exist).
Anything that can have a user-provided protocol conformance can be turned into an error with a single line of code and no forethought whatsoever.
For this reason, the error versus non-error distinction is practically nonexistent.
The Error protocol is essentially a marker protocol.
Exactly! To me, the term "type-safe" means (among other things) having information about what can safely be assumed to definitely be there, and what can safely be assumed to definitely NOT be there.
In this case, static type information in the form an exact set of error types fits the bill, while an absolutely arbitrary and completely un-actionable value wrapped in a marker protocol existential certainly does not.
If one simply has an instance of any Error, there's absolutely nothing they can do other than print it out and then cancel whatever they're doing. By definition, it's any error. Anything other than simple log-printing and triggering some sort of cancellation would require them to downcast it, which immediately invalidates the whole narrative about any Error being useful. The only reason why any Error is useful at all is to be able to execute an arbitrary throwing operation and rethrow its error without stopping to think why that error happened. At the very least, it's a lot better to do something like this instead:
enum MyError {
case firstPartFailure(any Error)
case secondPartFailure(any Error)
// ...
}
They're still retaining the exact error that occurred without necessarily having to care about what the error was, but they're at least no longer mindlessly rethrowing an unknown error, because they can't be bothered to actually think about proper error propagation. But even then, I'd prefer this instead:
enum MyError<FirstPartFailure, SecondPartFailure> where
FirstPartFailure: Error,
SecondPartFailure: Error
{
case firstPartFailure(FirstPartFailure)
case secondPartFailure(SecondPartFailure)
// ...
}
Because again, they have no business arbitrarily losing type information just because they're too lazy to actually think things through.
Exactly! One of the biggest wins in Swift, coming from Objective-C was static type information when it was needed. That is everywhere except for errors. I don't see any good reason why this would be an exception to the rule "have as much static type information as you want, but be able to lose it if you don't want it".