Generics
Generics let one declaration work with many concrete types while keeping those
types visible in the signature. The names in <...> are type placeholders that
the declaration can reuse in parameters, fields, return types, and bodies. Each
call or concrete type fills those placeholders with real types. Bounds go in a
where clause.
Generic functions
Section titled “Generic functions”A generic function introduces type parameters before its value parameters. The call site usually pins those types from the arguments and expected return type:
fn pair<'T, 'U>(first: 'T, second: 'U): ('T, 'U) {
(first, second)
}
fn main(): (String, Int) {
dbg pair("age", 30)
}
Here 'T is String and 'U is Int for this call. Another call can choose a
different pair of concrete types.
Generic types
Section titled “Generic types”Structs, enums, opaque types, extern types, interfaces, and type aliases can all introduce type parameters. Those names are available anywhere the declaration needs to talk about its parts:
struct Box<'T> {
field value: 'T
}
fn main(): Int {
box = Box{value: 42}
dbg box.value
}
Box<'T> says every Box carries one value, and the type of that value is the
same 'T wherever it appears. Box<Int> and Box<String> are different
concrete types made from the same generic definition.
Bounds with where
Section titled “Bounds with where”An unconstrained 'T can be stored, returned, and passed around, but it does not
promise any extra capabilities. A where clause adds those promises.
For now, read a bound like where 'T: Comparable as “'T must be a type that can
be compared.” Bounds name interfaces; the next chapter explains how interfaces
are defined and implemented. Here, the important part is the generic shape:
fn first_two_sorted<'T>(xs: List<'T>): List<'T> where 'T: Comparable {
xs
|> Iter.sort()
|> Iter.take(2)
|> Iter.to_list()
}
fn main(): List<String> {
first_two_sorted([3, 1, 4, 1, 5, 9, 2, 6])
|> dbg
first_two_sorted(["banana", "apple", "cherry"])
|> dbg
}
Both Int and String implement Comparable, so both calls satisfy
where 'T: Comparable.
Multiple bounds use and:
fn describe<'T>(value: 'T): String where 'T: Display and Debug
The type parameter has to satisfy every listed interface.
Relational bounds
Section titled “Relational bounds”A bound can mention another type parameter. That ties the choices together without requiring them to be the same type:
interface StepBy<'S> {
fn step_by(value: self, step: 'S): self
}
type Counter Int
impl StepBy for Counter {
fn step_by(value: Counter, step: Int): Counter {
Counter(Int(value) + step)
}
}
fn advance<'T, 'S>(value: 'T, step: 'S): 'T where 'T: StepBy<'S> {
'T.step_by(value, step)
}
fn main(): Counter {
dbg advance(Counter(10), 5)
}
advance<'T, 'S> introduces two independent type parameters. The bound
where 'T: StepBy<'S> says the chosen 'T must know how to step by the chosen
'S. In this example, 'T is Counter and 'S is Int.
That shape matters for APIs like ranges and dates: a value type might step by
an Int, a duration, or some other unit type instead of stepping by another
value of its own type.
The same idea lets operator interfaces infer a result that is not necessarily
the left-hand type. Add<'Rhs, 'Out> says what the right-hand operand may be
and what the expression returns; Subtract, Multiply, and Divide use the
same rhs/result shape:
type Day Int {
fn number(day: Day): Int {
Day(n) = day
n
}
}
type Days Int {
fn number(days: Days): Int {
Days(n) = days
n
}
}
impl Add<Days, Day> for Day {
fn add(lhs: Day, rhs: Days): Day {
Day(Day.number(lhs) + Days.number(rhs))
}
}
fn plus<'L, 'R, 'Out>(lhs: 'L, rhs: 'R): 'Out where 'L: Add<'R, 'Out> {
lhs + rhs
}
fn main(): Day {
dbg plus(Day(10), Days(4))
}
Here 'L is Day, 'R is Days, and 'Out is Day.
Bounds on declarations
Section titled “Bounds on declarations”where can appear on the generic declaration that needs the constraint:
struct Range<'T> where 'T: Comparable { field start: 'T field end: 'T fn contains?(r: Range<'T>, value: 'T): Bool { r.start <= value and value <= r.end } } derive Display for Box<'T> where 'T: Display
Constraints on a type declaration apply to that type everywhere. Constraints on
an impl, derive, or function are narrower: they apply only to that
implementation, derive, or function.
Generic interface functions can introduce their own type parameters too:
interface Ranked<'T> { fn item(value: self): 'T fn prefer<'K>(value: self, lhs: 'K, rhs: 'K): Bool where 'K: Comparable }
'T comes from Ranked<'T>. 'K comes from prefer<'K>. A type parameter must be
introduced before a where clause can constrain it.
Next: Interfaces & Dispatch.