13. Interfaces and generics
13.1 Interfaces#
interface Shape:
func area(self) -> float
func name(self) -> str
struct Circle: Shape:
var radius: float
func area(self) -> float:
return 3.14159 * self.radius ** 2.0
func name(self) -> str:
return "circle"An interface is a set of method signatures. A struct, enum or class declares its conformance at its declaration, : Shape, and provides every method with the exact signature. A struct's or enum's method for a requirement does not change self: an interface value is read through the interface, never changed through it. There are no default methods, no inheritance between interfaces beyond listing several, no associated types, and no downcast from an interface value to a concrete type.
13.2 Interface values and generics#
func total(shapes: list[Shape]) -> float:
var sum = 0.0
for shape in shapes:
sum += shape.area()
return sum
func largest[T: Ordered](values: list[T]) -> T?:
...
func show_sorted[T: Ordered & Display](values: list[T]) -> str:
...A value of a conforming type converts to an interface value where one is expected: a copy of the value behind the interface, with the ownership of a value (§10.5, docs/RUNTIME.md), so a class behind it lives while any copy of the interface value does. A call dispatches to the value's own method; an interface value has no ==, no ordering, no hash and no display of its own, and an optional of one is compared with none like any optional. A generic function takes type parameters in [...], each optionally bounded by interfaces joined with &, and is checked once against its bounds; inside it, a value of a parameter type has the bounds' methods, == with Equatable, < with Ordered, hash with Hashable, display with Display, and the operations every type has: it is bound, passed, returned, and held in tuples, optionals and collections. Type arguments are inferred from the arguments, those given to a parameter of a bare parameter type first, or written, largest[int](values); each must satisfy its bounds. The program gets one instance of the function per distinct type arguments; a generic function is called, never read as a value, and a method has no type parameters of its own.
A struct, enum, class or interface takes type parameters the same way, struct Pair[A, B], enum Option[T], class Stack[T], interface Container[T], and Pair[int, str] names an instance, a type of its own with the parameters replaced. A construction infers the arguments from the expected type or from the values given to the fields, init or the case's payload, Pair(first = 1, second = "one"); Option.empty needs the expected type known or the arguments written, Option[int].empty; a class with an init taking no value that fixes a parameter is written Stack[int](). An instance displays as Pair(first = 1, second = one), its generic name; a bound may name an instance, [I: Iterable[int]].
13.3 The closed protocols#
The compiler knows these interfaces, and syntax uses them:
| Interface | Methods | Used by |
|---|---|---|
Equatable | equals(self, other: Self) -> bool | ==, !=, in, keys |
Hashable | hashed(self) -> int with Equatable | map keys, set elements |
Ordered | compare(self, other: Self) -> int | < and friends, sort |
Display | display(self) -> str | print, str(x), f-strings |
Iterable[T] | iterator(self) -> Iterator[T] | for |
Iterator[T] | next(self) -> T? | for, while let |
Values get Equatable, Hashable, Ordered and Display structurally as §4.4 and §10.5 say; a type declares one only to replace the structural meaning, and a class must declare them to have them at all. for x in v over a value declaring Iterable[T] calls v.iterator() once and next() before every pass until it answers none; the iterator is a class, since a struct's method for a requirement may not change self (§13.1), and while let x = it.next() walks it by hand. A closed protocol names what a type declares, never a value's type: let e: Equatable = x is an error, and no program may declare one itself. A type that declares Equatable keys a map or sits in a set only when it declares Hashable beside it, so its hash agrees with its equals; Hashable and Ordered are declared beside Equatable, never alone. A value with a declared hashed hashes as the int it returns (docs/RUNTIME.md).