Trait Bounds
Trait bounds let you write generic functions that require certain capabilities from their type parameters. This enables compile-time checking that types support the operations you need, catching errors before your code runs.
Basic Syntax
Add trait bounds after a type parameter using a colon. The syntax [T: Trait] means "T must implement Trait".
# T must implement Hash
hashable[T: Hash](x: T) -> Int = hash(x)
# T must implement Eq
are_equal[T: Eq](x: T, y: T) -> Bool = x == y
# T must implement Show
describe[T: Show](x: T) -> String = "Value: " ++ show(x)
main() = {
# Works with any type implementing the required trait
println(show(hashable(42))) # Int implements Hash
println(show(are_equal("a", "a"))) # String implements Eq
println(describe(true)) # Bool implements Show
}
Multiple Constraints
Use + to require a type to implement multiple traits.
# T must implement both Hash AND Show
hash_and_show[T: Hash + Show](x: T) -> String = {
h = hash(x)
"hash(" ++ show(x) ++ ") = " ++ show(h)
}
# T must implement Hash, Eq, AND Show
full_info[T: Hash + Eq + Show](x: T, y: T) -> String = {
eq_str = if x == y then "equal" else "not equal"
show(x) ++ " and " ++ show(y) ++ " are " ++ eq_str
}
main() = {
println(hash_and_show(42))
# Output: hash(42) = 7298839827432
println(full_info("hello", "hello"))
# Output: hello and hello are equal
}
Multiple Type Parameters
Each type parameter can have its own constraints, separated by commas.
# Two parameters with the same bound
compare_hashes[T: Hash, U: Hash](x: T, y: U) -> Bool = hash(x) == hash(y)
# Different bounds on each parameter
show_hash[T: Show, U: Hash](x: T, y: U) -> String = {
show(x) ++ " has hash peer with hash " ++ show(hash(y))
}
# Mixed: one constrained, one unconstrained
show_first[T: Show, U](x: T, y: U) -> String = show(x)
main() = {
# compare_hashes works with different types
println(show(compare_hashes(42, 42))) # true (same Int)
println(show(compare_hashes("a", "a"))) # true (same String)
}
Compile-Time Checking
Trait bounds are checked at compile time. This is most useful for custom traits you define yourself. If you try to use a type that doesn't implement the required trait, you get a helpful error message.
# Define a custom trait
trait Printable
toText(x) -> String
end
printable[T: Printable](x: T) -> String = toText(x)
type MyType = MyType(Int) # No Printable impl
main() = {
x = MyType(42)
printable(x) # Compile error!
}
# Error: MyType does not implement trait `Printable`
Note: All types automatically have the builtin traits Hash, Eq, Show, and Copy. Trait bounds are most useful for custom traits.
Primitive Type Traits
Built-in primitive types automatically implement common traits, so you can use them with bounded functions right away.
| Type | Implements |
|---|---|
| Int | Hash, Eq, Show, Copy |
| Float | Eq, Show, Copy |
| Bool | Hash, Eq, Show, Copy |
| Char | Hash, Eq, Show, Copy |
| String | Hash, Eq, Show, Copy |
Practical Example: Generic Lookup
Here's a practical example: a generic lookup function that works with any key type that implements Eq.
# Generic lookup - K must implement Eq for comparison
type NotFound = NotFound
type Found = Found(V)
lookup[K: Eq, V](items: List, key: K) = match items {
[] -> NotFound
[(k, v) | rest] -> if k == key then Found(v) else lookup(rest, key)
}
# Custom types - all have builtin Eq
type UserId = UserId(Int)
type Email = Email(String)
main() = {
# Works with UserId keys
users = [(UserId(1), "Alice"), (UserId(2), "Bob")]
result1 = lookup(users, UserId(1))
println(show(result1)) # Found(Alice)
# Works with Email keys
emails = [(Email("a@b.com"), 100), (Email("x@y.com"), 200)]
result2 = lookup(emails, Email("x@y.com"))
println(show(result2)) # Found(200)
# Works with primitive keys too
scores = [("Alice", 100), ("Bob", 85)]
result3 = lookup(scores, "Bob")
println(show(result3)) # Found(85)
}
Bounds with Higher-Order Functions
Trait bounds work seamlessly with higher-order functions and lambdas.
# Apply a function and show the result
apply_and_show[T: Show, U: Show](f: T -> U, x: T) -> String = {
result = f(x)
show(x) ++ " -> " ++ show(result)
}
main() = {
double = x => x * 2
println(apply_and_show(double, 21))
# Output: 21 -> 42
negate = b => if b then false else true
println(apply_and_show(negate, true))
# Output: true -> false
}
Summary
-
1.
Basic bounds:
[T: Trait]requires T to implement Trait -
2.
Multiple traits: Use
+to combine:[T: Hash + Eq] -
3.
Multiple params: Each can have bounds:
[T: Hash, U: Show] - 4. Compile-time safety: Errors caught before runtime
-
5.
Builtin traits: All types have
Hash,Eq,Show,Copyautomatically