Bounds
A bound restricts a type parameter to types that implement one or more traits, written inline or after where, and a member can carry a bound of its own that is not a requirement on every implementor.
Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite
A bound says which traits a type parameter's type has to implement. Without one, nothing is known about the type
except that it is one - not even == is available on it.
Example#
fn largest<Item: Compare>(values: List<Item>): Item? {
values.get(0).map { first => values.fold first { a, b => a.max b } }
}
fn printSorted<Item>(items: List<Item>) where Item: Compare & Show {
const ordered = items.sorted { _ }
for item in ordered {
print item
}
}
printSorted([3, 1, 2])
print largest([3, 1, 2])Syntax#
fn <name><Item: <Trait>>(...) inline, one parameter
fn <name><Item>(...) where Item: <Trait> after where, same meaning
fn <name><A, B>(...) where A: <Trait>, B: <Trait> & <Trait> several parameters, `&` combines traits
fn <name>(...): <Type> where Item: <Trait> a member's own boundRules#
A bound is written inline, right after the parameter's name (
<Item: Compare>), or afterwhere(where Item: Compare). The two forms mean the same thing;whereis what several parameters or several traits on one parameter read best with.Several traits on one parameter combine with
&.where Item: Compare & Showis the same&that intersects traits in a type position; see Trait intersections.wherecan carry the bounds of more than one parameter, separated by,.fn zip<Left, Right>(lefts: List<Left>, rights: List<Right>): List<(Left, Right)> where Left: Show, Right: ShowA member of a trait can carry a
whereclause of its own, and then it is not a requirement on every implementor - it exists only where the clause holds.Iterate<Item>declaresfn toSet(): Set<Item> where Item: Hash, so everyIteratehas the method, but calling it is only legal whereItemhappens to beHash:const numbers: List<Int> = [3, 1, 2] print numbers.toSet()Kompiliert nichttype Task { action: () => Void } const tasks: List<Task> = [] const unique = tasks.toSet() // error: `Task` does not implement `Hash`A bound is checked at the call, not inside the declaration that asks for it.
largest<Item: Compare>type checks its own body against exactly the membersComparepromises, and a call that cannot supply aComparetype is rejected at the call.fn largest<Item: Compare>(values: List<Item>): Item? { values.get(0).map { first => values.fold first { a, b => a.max b } } } print largest([3, 1, 2])Kompiliert nichtfn largest<Item: Compare>(values: List<Item>): Item? { values.get(0).map { first => values.fold first { a, b => a.max b } } } type Task { action: () => Void } const tasks: List<Task> = [] print largest(tasks) // error: `Task` does not implement `Compare`
What this is not#
A bound is not a cast, and it does not narrow what a value already is. Inside largest<Item: Compare>, item
stays an Item - the bound only adds the members of Compare to what can be called on it, it does not turn item
into a Compare-typed value. Nothing about largest changes when it is called with an Int instead of some other
Compare type, and nothing inside it can tell the two apart.
fn describe<Item: Show>(value: Item): String {
"the value is {value}"
}
print describe(5)trait Loud {
fn shout(): String
}
type Silent {
loud: Loud
}
type Robot with Loud {
fn shout(): String {
"BEEP"
}
}
fn describe<Item: Show>(value: Item): String {
"the value is {value}"
}
print describe(Silent(Robot()))
// error: `Silent` does not implement `Show`Related#
- Type parameters - where a parameter is declared, before it is bounded.
- Trait intersections - the
&a bound and a type position share. - Traits as types - a bound compared to a trait used as a parameter's own type.
- Inference - what a bound does and does not help the compiler infer.