Generics

A generic function or type is parameterized over one or more types. The same code works on Int, String, User, or anything else, with the compiler checking each use is type-correct.

Generic functions

func identity[T](value: T) -> T { value } identity(42); // T = Int identity("hello"); // T = String

[T] after the name introduces a type parameter. Inside the function, T is just a stand-in for whatever the caller passed.

Generic types

A struct or enum can be generic too:

struct Box[T] { var value: T } let intBox = Box(value: 42); // Box[Int] let stringBox = Box(value: "hi"); // Box[String]

Use one type parameter per "kind of thing" the type holds. Multiple parameters are written [K, V] and so on.

Constraints

Pure generics work on any type. Once a function needs to do something with a type parameter (compare, hash, draw), constrain it to a protocol:

func deduplicate[T](items: [T]) -> [T] where T: Hashable { var seen = Set[T](); var result: [T] = []; for item in items { if seen.insert(item) { result.append(item); } } result }

The where clause is how Kestrel says "T can be anything, as long as it's Hashable." The constraint is what lets Set[T] hash the items here. See Where Clauses for the longer story.

Associated types

Sometimes a protocol has a type that depends on the conforming type — Container.Item, Iterator.Item. Those are associated types, declared with type inside the protocol. See Associated Types.

When to reach for generics

  • The code does the same thing for many types and the operations are protocol-defined.
  • A collection holds many of one kind of thing, and the kind shouldn't be hardcoded.
  • A function takes a callback and you want to preserve the type through the call.

If you find yourself with one generic that has so many constraints it's effectively a single concrete type, you didn't need a generic.

Subpages