Part 13: Generics

Generic in Part 4 gave a function a type parameter, so one body could serve int, float64 and char. This part takes the idea all the way. Types get type parameters of their own — a Pair<T>, a Reading<T> — and so do their methods. Bounds turn "any T" into "any T that can do this", which is what lets a generic call methods, compare scores or print its values. And the language's own forms, T?, T ! E and A | B, turn out to be generic too, which is how a helper can be written once for every optional or every fallible there will ever be.

What you will learn

  • Declaring structs and variants with type parameters, and naming the type arguments in a literal.
  • Giving a generic type methods with extend Labeled<T>, including methods with type parameters of their own.
  • Bounding a type parameter by an interface, <T: Scored>, and where the compiler checks the bound.
  • Requiring several interfaces at once with <T: Scored + Display>.
  • Writing reusable helpers over T ! E and T?, which cannot be extended.
  • Building sums from type parameters, T | U, and why they collapse when T and U are the same.

What a bound decides

Everything in this part comes back to one question: what may the body do with a T? The bound is the answer, and the compiler checks it where the generic is used.

flowchart LR
    t(["A type parameter T"]) --> none["No bound: &lt;T&gt;<br/>store, pass, return a T<br/>(13.1–13.2)"]
    t --> one["One bound: &lt;T: Scored&gt;<br/>+ call Scored's methods<br/>(13.3)"]
    t --> two["Several: &lt;T: Scored + Display&gt;<br/>+ everything each one provides<br/>(13.4)"]
    one --> call{"At each call:<br/>does the type argument<br/>implement every bound?"}
    two --> call
    call -- "yes" --> ok["compiled for that type,<br/>calling its methods directly"]
    call -- "no" --> err["error at the call,<br/>naming the missing method"]
You want to…WriteLesson
describe "two of something"struct Pair<T> { … }Generic type
give every Pair<T> a methodextend Pair<T> { … }Generic method
call a method on a T<T: Interface>Generic bound
call methods from two interfaces<T: A + B>Multiple bounds
write one helper for every optional or falliblefunc F<T, E>(outcome: T ! E)Generic outcome
return one of two types-> T | U, matched with elseGeneric sum

Lessons

LessonWhat you will learn
13.1Generic typea struct with a type parameter: Pair<T>
13.2Generic methodmethods on a generic type, and methods with type parameters of their own
13.3Generic boundrequire a type parameter to implement an interface
13.4Multiple boundsrequire several interfaces at once with A + B
13.5Generic outcomewrite helpers that work for any optional or result
13.6Generic sumgeneric sums, and what happens when both members are the same type

Before you start

Finish Part 12: Interfaces: a bound is an interface, and Display is the one most generics end up needing. The lessons also build on Generic and Callback from Part 4, Struct and Variant match from Part 6, the outcome tools of Part 8 and Part 9, and Sum type from Part 10. Each lesson's package is in the Examples repository's Generics/ folder:

cd Examples/Generics/GenericType
rux run

After this part

Part 14: Text puts generics to work straight away: String, StringView and StringBuilder are ordinary types from the Text package, and every value you print goes through the Display interface you bounded on here. Later, Part 17: Collections is built from generic types — a vector, a map and a set of any element type.

For the full rules behind this part, see Generic functions, Interfaces and Methods in the Rux Reference.