The topic of mutually referenced default implementations came up in the thread Combining hashes - #89 by lorentey, specifically in the context of hashValue
and the proposed hash(into:)
. @xwu pointed out that in some cases the standard library provides such mutually referenced default implementations:
We already have other circumstances in the standard library where (for convenience or backwards compatibility) two protocol requirements have default implementations that reference each other (heterogeneous generic operators and homogeneous ones;
Comparable
requirements andStrideable
requirements). Users who stumble into this situation find that their “conforming” types compile but then crash at runtime.
The result is that the mutually referenced conforming members crash due to infinite mutual recursion unless a conforming type provides an explicit implementation of at least one of the requirements. This situation leaves a lot to be desired. @xwu continued:
The solution I’m thinking of is teaching the compiler to recognize these circular references as scenarios in which either requirement A or B must be implemented at minimum, rather than blindly permitting both default implementations to call each other endlessly. (Of course, this would have to be limited to default implementations that are inlinable–i.e., where the compiler can actually determine what’s in the body of the implementations.) As a bonus, if one of these default implementations is marked as deprecated, the compiler might always recommend that the user implement the other one.
With ABI stability, such a feature would be hugely important in allowing us to evolve protocols in backward-compatible ways. For this particular circumstance, it would permit us to rip out custom compiler magic except where it pertains to synthesized conformance to
Hashable
.
@Karl suggested a new constraint that would allow us to make the default implementations mutually exclusive which @lorentey found interesting in the context of his use case for Hashable
:
Supporting either/or protocol requirements by providing default implementations based on the presence/absence of “direct” implementations for other requirements sounds like an intriguing idea. It needs to be explored further: For example, in the case of
Hashable
, what wouldextension Hashable where Self.implements(hashValue)
mean, exactly? When is an implementation of hashValue not good enough for implements? Default implementations aren’t currently marked as such.
One potential issue this constraint wouldn't address directly is a manually written implementation that calls through to the mutually recursive default. It might be interesting to explore how we might be able to prevent that mistake as well.
The purpose of this thread is to explore the design space and see if we can reach a consensus on what a solution should look like.