Generic code
Write an algorithm or container once and use it with concrete types.
On this page
A generic declaration names a type parameter instead of choosing a particular type. The compiler checks the declaration's operations for the concrete types used by callers.
Parameterize a function#
fn repeat[T](value: T) Array[T] {
return Array[T]{ value, value }
}
fn main() {
let names = repeat[String]("Ada")
let counts = repeat[int](3)
println(names[0])
println(counts.sum()) // 6
}
The [T] after the function name introduces the type parameter. In repeat[String], every use of T in this instance becomes String.
Repeating a class value repeats its reference. Use an explicit copy operation when you need independent objects; generic syntax does not change the assignment rules.
Infer a type from an argument#
Use $T in a parameter type to introduce a type parameter that the compiler infers from a call:
fn first(items: Array[$T]) T !LookupError {
return items.get(0) !>
}
fn main() {
let names = Array[String]{ "Ada", "Sam" }
println(first(names) !? "Nobody")
}
Here, the argument is an Array[String], so T is String and the function returns a string. The lookup error still needs handling if the array is empty.
Parameterize a class#
class Box[T] {
value: T
fn get() T {
return this.value
}
}
fn main() {
let name = Box[String] { value: "Ada" }
let score = Box[int] { value: 42 }
println(name.get())
println(score.get())
}
Use generic containers when the stored type should remain known to callers. Use an interface when callers only need a shared set of operations, or a tagged union when they must handle a specific set of alternatives.
Keep type requirements visible#
A generic function can only be instantiated with a type that supports the operations its body performs. For example, adding two T values requires an addition operation for that type. Begin with a concrete implementation, then introduce a type parameter where the same behavior makes sense for several types.
The compiler also exposes type metadata, such as T.$is_nullable, for #if conditions inside generic code. Use this when the generated behavior really needs to differ by type; ordinary application code can usually keep the same implementation.