Where Clauses

A where clause constrains type parameters. You write it after the parameter list (or after the type, for type-level constraints) and the compiler treats anything inside as a precondition.

Conformance constraints

func deduplicate[T](items: [T]) -> [T] where T: Hashable { // ... }

Reads: "deduplicate works on any T, as long as T is Hashable." Inside the body you can hash any T — drop it in a Set, use it as a Dictionary key — because the constraint guarantees it.

Multiple constraints

Stack with commas:

func sortAndCount[T](items: [T]) -> Int where T: Comparable, T: Hashable { // ... }

Or, equivalently, refine a protocol that includes both:

protocol Sortable: Comparable, Hashable {} func sortAndCount[T](items: [T]) -> Int where T: Sortable { /* ... */ }

Reach for refinement when the combination is meaningful and reused. Reach for where T: A, T: B when it's a one-off.

Equality constraints

A where clause can also pin associated types:

protocol Container { type Item func count() -> Int func item(at index: Int) -> Item } func zip[A, B](a: A, b: B) -> [(A.Item, B.Item)] where A: Container, B: Container, A.Item = B.Item { var result: [(A.Item, B.Item)] = []; var i = 0; while i < a.count() and i < b.count() { result.append((a.item(at: i), b.item(at: i))); i += 1; } result }

A.Item = B.Item — written with a single = — says the two containers must hold the same kind of element. The compiler enforces it at the call site.

On extensions

The same where syntax constrains an extension to apply only when conditions are met:

<!-- sample: continue --> 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() shows up on containers of Ints but not on containers of Connections.

When constraints get complicated

If your where clause is more than three lines, the abstraction is probably trying to do too much. Either:

  • Split the function into a more specific version, or
  • Define a protocol that captures the combination ("X, Y, and Z all hold; let's call that Foo").

A long constraint list is the abstraction begging you for a name.