Trait Bounds
A bare type parameter T has no methods — the compiler does not know what T
can do. A trait bound tells it: T is some type that implements a specific
trait. Once the bound is in place, the body can use all that trait's operators
and methods on values of type T.
Bounds are written inline (fn f<T: Ord>(x: T)) or in a where clause —
equivalent forms; where reads better when several bounds apply at once. The
compiler checks bounds at compile time and erases them at runtime, so they add
no overhead:
max<T: Ord> uses >; sum<T: Add> uses +; announce uses where T: Add + Display for multiple bounds.
// Feature: trait bounds on generics — `<T: Trait>` and `where`
// Syntax: `fn f<T: Ord>(...)` or `fn f<T>(...) where T: Ord`
// When to use: when a generic function needs to DO something with `T`
// (compare, add, print). The bound is what lets the body use `>`, `+`,
// etc. on a `T` — without it the compiler rejects the operator (TE821),
// and calling with a type that doesn't satisfy the bound is TE820.
//
// Std traits and the operators they enable:
// Eq -> == != Ord -> < > <= >= Add -> + Sub -> -
// Mul -> * Div -> / Mod -> % Neg -> unary -
// Display -> print/interpolation Hash -> Map key
// Primitives implement them out of the box (int/float: all; str: Eq, Ord,
// Add (concat), Display, Hash; bool: Eq, Display, Hash).
//
// Bounds are checked at COMPILE time and then erased — they add no runtime
// cost. See docs/errors/TE820, TE821, TE822.
// `T: Ord` lets the body use `>` on `T`.
fn max<T: Ord>(a: T, b: T) -> T {
if a > b { a } else { b }
}
// `T: Add` lets the body use `+` on `T`.
fn sum<T: Add>(a: T, b: T) -> T {
return a + b
}
// `where` form — equivalent to the inline bound, nicer with several params
// or several bounds. Here `T` must be both addable and printable.
fn announce<T>(label: str, a: T, b: T) -> T where T: Add + Display {
let total = a + b
print("{label}: {total}")
return total
}
// Ord works for any ordered primitive.
print(max(3, 7)) // 7
print(max("apple", "kiwi")) // kiwi (lexicographic)
print(max(2.5, 1.5)) // 2.5
// Add works for numbers and string concatenation.
print(sum(10, 32)) // 42
print(sum("foo", "bar")) // foobar
announce("score", 40, 2) // score: 42
// expected:
// 7
// kiwi
// 2.5
// 42
// foobar
// score: 42
The common std bounds and the operators they unlock:
| Trait | Enables |
|---|---|
Ord |
< > <= >= |
Eq |
== != |
Add |
+ |
Sub |
- |
Mul |
* |
Div |
/ |
Display |
print, interpolation |
Primitives implement all applicable bounds out of the box: int and float
satisfy every numeric and comparison trait; str satisfies Eq, Ord, Add
(concatenation), Display, and Hash.
Challenge
Write a generic function clamp<T: Ord>(x: T, lo: T, hi: T) -> T that returns
lo if x < lo, hi if x > hi, and x otherwise. Test it with integers
and strings.