Introduction
- Implementation: apple/swift#23489
The objective is to lift the restriction on attaching where
clauses to declarations that themselves do not carry a generic parameter list explicitly, but inherit the surrounding generic environment. Simply put, this means you no longer have to worry about the 'where' clause cannot be attached
error within a generic context.
struct Box<Wrapped> {
func sequence() -> [Box<Wrapped.Element>] where Wrapped: Sequence { ... }
}
Note: Only declarations that can already be generic and support being constrained via a conditional
extension are covered by this enhancement. Properties and new constraint kinds are out
of scope for this document. For example, the following remains an error:protocol P { // error: Instance method requirement 'foo(arg:)' cannot add constraint 'Self: Equatable' on 'Self' func foo() where Self: Equatable }
Motivation
Today, where
clauses on contextually generic declarations can only be expressed indirectly by placing them on conditional extensions. Unless the constraints are identical, every such declaration requires a separate extension. This apparent dependence on extensions is an obstacle to stacking up constraints or grouping semantically related APIs and usually becomes a pain point in heavily generic code that unnecessarily complicates the structure of a program for the developer and the compiler.
Leaving ergonomic shortcomings behind, it is only natural for a where
clause to work anywhere a constraint can be meaningfully imposed, meaning both of these layout variants should be possible:
// 'Foo' can be any kind of nominal type declaration. For a protocol,
// 'T' would be an associatedtype.
struct Foo<T> {}
extension Foo where T: Sequence, T.Element: Equatable {
func slowFoo() { ... }
}
extension Foo where T: Sequence, T.Element: Hashable {
func optimizedFoo() { ... }
}
extension Foo where T: Sequence, T.Element == Character {
func specialCaseFoo() { ... }
}
extension Foo where T: Sequence, T.Element: Equatable {
func slowFoo() { ... }
func optimizedFoo() where T.Element: Hashable { ... }
func specialCaseFoo() where T.Element == Character { ... }
}
A move towards «untying» generic parameter lists and where
clauses is an obvious and farsighted improvement to the generics system with numerous future applications, including contextually generic computed properties, opaque types, generalized existentials and constrained protocol requirements.
Source compatibility and ABI stability
This is an additive change with no impact on the ABI and existing code.
Effect on API resilience
For public declarations in resilient libraries, switching between a constrained extension and a «direct» where
clause will not be source-breaking, but will break the ABI due to subtle mangling differences.