I don't know if it was already mentioned that a non-throwing function is a subtype of a throwing function with the same signature. This allows non-throwing protocols to inherit from throwing protocols:
protocol Throwing {
func f() throws
}
protocol NotThrowing: Throwing {
// A non-throwing refinement of the method
// declared by the parent protocol.
func f()
}
struct S: NotThrowing {
func f() { print("S") }
}
// Compiles, and prints "S" thrice
S().f()
(S() as NotThrowing).f()
try (S() as Throwing).f()
Based from this example, I suggest "typed throw" allows the definition of more complex hierarchy of intermediate protocols. Maybe something as below:
protocol ColoredError: Error { }
class BlueError: ColoredError { }
class DeepBlueError: BlueError { }
protocol ThrowingColoredError: Throwing {
// Refinement
func f() throws ColoredError
}
protocol ThrowingBlueError: ThrowingColoredError {
// Refinement
func f() throws BlueError
}
protocol ThrowingDeepBlueErrorError: ThrowingBlueError {
// Refinement
func f() throws DeepBlueError
}
I'm not 100% sure about the above code, because it contains both protocol and subclass inheritance. And unlike Error
, the ColoredError
existential does not conform to the ColoredError
protocol. So I may well miss something. Experts of Swift subtyping rules know better.
Yet I just wanted to stress the importance of subtyping in chains of inherited protocols, and the preservation of a high level of language consistency that maximally extends existing language features. Blind spots are hard to fix later.