Typed throw functions

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.

2 Likes