Extending
Protocol extensions add behavior to all conforming types in one place.
Adding methods to every conformer
protocol Counter {
var value: Int { get }
}
extend Counter {
public func isZero() -> Bool {
self.value == 0
}
public func isPositive() -> Bool {
self.value > 0
}
}
Every type that conforms to Counter now has isZero and isPositive for free, no extra work at the conformance site.
Constrained extensions
You can add behavior only when conditions are met. Use a where clause:
protocol Container {
type Item
func count() -> Int
func item(at index: Int) -> Item
}
extend Container where Item: Comparable {
public func max() -> Optional[Item] {
if self.count() == 0 { return .None };
var best = self.item(at: 0);
for i in 1..<self.count() {
let candidate = self.item(at: i);
if candidate > best { best = candidate };
}
.Some(best)
}
}
max() exists on any conformer of Container whose Item is Comparable — for example a Stack[Int]. A Stack[File] won't have it (assuming File isn't Comparable). The compiler tracks this at the type level — calling max() on a non-Comparable container is a compile error, not a runtime one.
Extension vs default method
These look similar but have different intents:
- A default method (Default Methods) supplies a fallback for a required member. Conforming types can override it.
- An extension method adds a new member that isn't part of the protocol's contract. Conforming types can't override it — extension methods aren't dispatched virtually.
Pick a default method when the operation is genuinely part of the protocol and individual types might need to specialize. Pick an extension method when the operation is convenience, derived from existing requirements, and shouldn't be customized per type.
For struct-side extensions (adding methods to your own types or stdlib types), see Extending Types.