Templates
Volt’s generics are templates, like C++’s: each set of generic arguments makes its own copy of the function or type, compiled as if written by hand. There are no boxed values and no runtime dispatch.
Generic functions
Section titled “Generic functions”The generic parameters go in <...> on the line before the declaration. T: type takes a type.
Arguments are usually inferred from the call; f<T>(...) gives them explicitly.
use std::io;
<T: type>fn max(a: T, b: T) -> T { if (a > b) { return a; } return b;}
fn main() -> void { std::println("{} {} {}", max(3, 9), max(2.5, 1.0), max<u8>(200, 100));}// expect: 9 2.5 200A template’s body is checked for each set of arguments it’s used with, so it can use anything the
argument supports: max works for every type with >. Using it with a type that doesn’t have
> reports the error at that use, and names each template instance on the way to it, back to
the call in code that isn’t a template: sorting a struct with no cmp points into std, and at
your sort call. To state the requirements up front, bound the parameter with a
trait.
Generic types
Section titled “Generic types”Structs, enums, error sets and type aliases take generic parameters the same way, with defaults.
use std::io;
<T: type>struct pair { first: T; second: T;}
<T: type>attach fn swapped(this: pair<T>) -> pair<T> { return { first: this.second, second: this.first };}
<T: type>enum maybe { SOME: T, NONE,}
fn main() -> void { val p: pair<str> = { first: "a", second: "b" }; val q = p.swapped(); val m: maybe<i32> = .SOME(4); std::println("{} {} {}", q.first, q.second, m);}// expect: b a SOME(4)Constant parameters
Section titled “Constant parameters”A generic parameter can be a value known at compile time instead of a type: N: usize,
C: i32 = 3, S: str. Array lengths are the usual use. A parameter marked comptime among a
function’s ordinary ones, fn repeat(comptime n: i32, c: str), works the same way (see
Comptime).
use std::io;
<T: type, N: i32>fn sum(xs: T[N]) -> T { var s: T = 0; for (x) in xs { s += x; } return s;}
fn main() -> void { val a: i64[4] = { 1, 2, 3, 4 }; val b: f64[2] = { 0.5, 0.25 }; std::println("{} {}", sum(a), sum(b));}// expect: 10 0.75Specialization
Section titled “Specialization”A declaration with <...> after its name covers particular arguments and is used instead of the
generic one. A full specialization names concrete types; a partial one names a shape, like “any
reference”.
use std::io;
<T: type>fn describe(v: T) -> str { return "a value"; }fn describe<bool>(v: bool) -> str { return "a bool"; }
<T: type>struct holder { value: T; }
<T: type>struct holder<T&> { value: T*; } // for every holder<T&>: store a nullable pointer
fn main() -> void { val h: holder<i32&> = { value: null }; std::println("{} {} {}", describe(1), describe(true), h.value == null);}// expect: a value a bool trueOverloaded templates are picked as functions are:
between versions that fit equally well, the more specific one wins (one for boxed<T> over one for
any T, one whose parameter has a trait bound over one without), and two just as specific are an
ambiguous call.
Args: type... takes any number of types, and args: Args... the matching values. A
comptime for over a pack unrolls: each iteration has its own type.
use std::io;
<Args: type...>fn show_all(args: Args...) -> void { comptime for (a) in args { std::print("[{}] ", a); } std::println("");}
fn main() -> void { show_all(1, "two", 3.5, true); show_all();}// expect: [1] [two] [3.5] [true]// expect:Methods on every type
Section titled “Methods on every type”An attached function whose receiver is a generic parameter attaches to every type. std uses this
for T::new(value), which boxes any value.
use std::io;
<T: type>attach fn describe_twice(this: T) -> void { std::println("{} {}", this, this);}
fn main() -> void { 42.describe_twice(); "ok".describe_twice();}// expect: 42 42// expect: ok okReturn-type overloads
Section titled “Return-type overloads”Overloads that differ only in the return type (see Functions)
work for templates too: <T: type> attach fn make(static this: T) -> T and a make returning
T? can both exist, and the context picks.