Conditional conformances make Swift generic wrappers compose
SE-0143 lets a generic type adopt a protocol when its arguments satisfy the necessary requirements. Its restrictions preserve a single answer to conformance questions at compile time and at runtime.
Swift can compare two arrays of integers, but that does not mean an array of integers satisfies a generic requirement for equality. That gap becomes visible when another array wraps those arrays. A comparison that works for the elements stops composing at the next level. Doug Gregor's conditional-conformance proposal addresses this mismatch between an operation available on a particular generic type and a protocol conformance that other generic code can use.
The acceptance announcement for SE-0143 places the proposal in Swift 4 phase 1. Joe Groff reports an initial review from September 28 through October 7, followed by a revised review from November 9 through November 15. Acceptance establishes the design direction. The examples below describe the accepted proposal, not functionality available in the current Swift 3 compiler.
I find Swift's design most persuasive when a type expresses a useful restriction without making ordinary code harder to read. Conditional conformances fit that preference. A wrapper can promise exactly the operations justified by the value it contains, and generic clients can use that promise through the same protocol machinery they already use elsewhere.
Equality needs a conformance
The accepted revision of SE-0143 describes the existing array problem precisely. Swift already provides an equality operator for arrays whose elements conform to Equatable. However, the array type itself does not conform to Equatable. The operator and the conformance are separate declarations with different consequences for generic code.
A generic function constrained to an equatable argument needs evidence that its argument type conforms to the protocol. Finding a suitable equality operator at one concrete call site does not supply that evidence. Likewise, an outer array's equality operation needs its element type to be equatable. If the element type is itself an array, the missing conformance prevents the comparison even though comparing the inner arrays directly is possible.
The proposal permits the following declaration shape. The omitted equality implementation is deliberately outside this illustration, which shows the conformance rule.
extension Array: Equatable where Element: Equatable {
// The equality implementation satisfies Equatable here.
}
The where clause limits the conformance to array specializations with equatable elements. It does not claim that an array containing an arbitrary type supports equality. A generic function accepting T: Equatable can consequently accept an array of equatable elements under the proposed rules. An array of a type without that conformance still fails the requirement.
This gives nesting a compositional explanation. If an inner array qualifies because its elements are equatable, the outer array can qualify because its elements are those qualifying arrays. The rule applies at each level without a separately written equality overload for every nesting depth. The important change is the reusable protocol relationship that makes this reasoning possible.
The syntax already exists in Swift's grammar. According to the proposal, the compiler currently rejects a constrained extension with an inheritance clause. SE-0143 removes that restriction for extensions of structures, enumerations and classes. Acceptance therefore gives meaning to a combination of existing constructs, although implementing the semantics requires more than a parser change.
Wrappers can retain their underlying capabilities
Lazy collections provide the proposal's larger library example. Swift 3 has LazySequence, LazyCollection, LazyBidirectionalCollection and LazyRandomAccessCollection. Overloads of the lazy property choose among these types according to the capabilities of the receiver. Each wrapper must provide or forward the operations expected at its particular level of the collection hierarchy.
That organization duplicates work because a more capable collection still needs the operations of a less capable collection. A wrapper for random access must account for the requirements below random access as well. The multiplication of wrapper types expresses a relationship between generic arguments and capabilities indirectly, through a set of distinct declarations and overloads.
Conditional conformances allow one generic wrapper to start with a sequence requirement and acquire collection conformances as its underlying type supplies them. A bidirectional base can justify a bidirectional wrapper. A random-access base can justify the stronger conformance. The proposal presents this as a way to reduce repetition in the standard library, not as a guarantee that every library refactoring has already happened.
For my purposes, this is the strongest argument for the change. Swift's standard collections already make value-oriented code attractive, and generic adapters should preserve the useful properties of their inputs. Requiring several public wrapper types to express that relationship makes the library harder to understand without adding information about the operation being performed.
There is a compatibility cost to simplifying those declarations. SE-0143 distinguishes adding useful conformances from removing existing wrapper types. A new conformance can improve composition additively. Collapsing public wrappers can affect source and binary compatibility because clients may name the old types. The proposal discusses generic type aliases as a possible source-compatibility mitigation, while recognizing that binary compatibility has separate requirements.
Runtime casts must answer the same question
Conditional conformance also affects values whose concrete types are hidden behind Any. The proposal gives an example in which an array conforms to a protocol only when its element type conforms. A conditional cast must inspect that requirement before it can report success. Static checking alone cannot supply the whole feature.
Here is the proposal's mechanism with its example kept compact. These declarations illustrate the accepted language design.
protocol P {
func doSomething()
}
struct S: P {
func doSomething() { print("S") }
}
extension Array: P where Element: P {
func doSomething() {
for value in self {
value.doSomething()
}
}
}
A function receiving Any and attempting value as? P must determine whether the hidden value's type meets the conditional rule. For an array of S, the lookup can find S's conformance and establish the array's conformance. For the proposal's array of integers, there is no corresponding conformance to this example protocol, so the cast fails.
This runtime behavior explains why the proposal connects conditional conformances to ABI stability. A runtime must support evaluating the extra requirements during dynamic conformance lookup. If an older runtime lacks that support, a later compiler cannot simply assume that dynamically queried conditional conformances will work there. The proposal argues for including the support before declaring the ABI stable, or at least providing a runtime hook that permits a later implementation.
The distinction matters for library authors because a protocol relationship can be observed through both generic constraints and dynamic casts. A design that worked only in statically visible cases would expose a different set of capabilities depending on how the value reached the caller. SE-0143 explicitly includes the dynamic case in the intended semantics.
A single conformance limits ambiguity
The accepted design retains Swift's prohibition on making the same type conform to the same protocol more than once. Conditional requirements do not create an exception. A wrapper cannot declare one Equatable conformance for wrapped values supporting equality and another for wrapped values supporting a separate identity protocol. A value satisfying both sets of requirements would make the choice ambiguous.
The restriction also excludes pairs that appear disjoint, such as one conformance for a wrapper around Int and another around String. This is deliberately stricter than banning obvious overlap. The proposal keeps a single conformance model instead of introducing a general system for selecting among independent conditional declarations.
Allowing overlap would require rules for deciding which conformance wins, including when several requirements hold together. Authors might need extra declarations for combinations of requirements. Runtime casting would need comparable disambiguation machinery, because conformance questions remain observable after static type information has been hidden. The proposal identifies both reasoning complexity and runtime cost as concerns.
I prefer that restriction for an initial design. The array equality problem and the wrapper duplication problem already justify conditional conformance. Solving them does not require a second language feature for ordering competing conformances, and keeping one answer makes a generic declaration easier to assess.
Protocol extensions remain outside the change too. Extending Collection to make every qualifying collection equatable would intersect with individual types' own equality conformances. That would reintroduce the overlap problem across a much broader group of types. SE-0143 therefore permits conditional conformance on nominal types while leaving protocol-to-protocol extensions for separate consideration.
Inherited protocols introduce a final constraint on declaration design. When two conditional conformances imply a common inherited protocol under unrelated requirements, the compiler cannot silently choose either requirement set. The proposal requires an explicit conformance for that inherited protocol when the constraints cannot be ordered. Library authors must state the common requirement broadly enough to support both child conformances. This keeps the inheritance relationship valid for every specialization that claims either child protocol.
The proposal also preserves a distinction between protocol requirement dispatch and concrete overload resolution. A more specialized method visible for a concrete type does not automatically replace the implementation chosen to satisfy a protocol conformance under broader constraints.