Generics
Generics allow you to create a single definition that can apply to multiple types. They are created by adding type parameters inside angle brackets <> in your definitions. You can add generics to functions, enums, and structs.
Functions
Section titled “Functions”Adding type parameters to function definitions allows you to use them in the input and output types:
fn map<type T, type U>(T[] arr, (fn(T) -> U) apply) -> U[] { mut U[] new_arr = [] for i in arr { new_arr = new_arr <> [apply(arr[i])] } return new_arr}When a generic function is called, its type parameters must be written explicitly:
test "generic function call" { int[] arr = [1, 2, 3, 4, 5] str[] arr2 = map<int, str>(arr, fn(int n) -> str { return "{n}" })
assert arr2 == ["1", "2", "3", "4", "5"]}Adding type parameters to enums allows you to use them as the types inside enum members:
enum Result<type T, type E> { Ok(T), Err(E), None,}When using the enum as a type, you must specify the type parameters explicitly:
Result<int, str> res = Result.Ok(1)if res { Result.Ok(n) -> { // `n` is of type `int` } Result.Err(e) -> { // `e` is of type `str` } Result.None -> {}}Structs
Section titled “Structs”Adding type parameters to structs allows you to use them as the types for their fields:
struct Data<type T> { T data}When using the struct as a type, you must specify the type parameters explicitly:
test "generic struct" { Data<str> data = Data<str>{.data = "hello"} assert data.data == "hello"}Chaining generics
Section titled “Chaining generics”You can of course chain together generics with different types and pass type parameters through them:
struct Data<type T> { T data}
enum Result<type T, type E> { Ok(T), Err(E),}
Result<Data<str>, str> val = Ok(Data<str>{.data = "hello"})
if val { Result.Ok(v) -> { @println("{v.data}") } // outputs "hello" Result.Err(e) -> { @eprintln("error: {e}") }}Interfaces
Section titled “Interfaces”You can add constraints on your type parameters using interfaces.