Generics
Full generic type system with constraints and type inference.
Generic Structs
// Single type parameter
pub struct Box<T> {
value: T
}
// Multiple type parameters
pub struct Pair<A, B> {
first: A,
second: B
}
// With constraints
pub trait Layout {
width: I32
}
pub struct Container<T: Layout> {
items: [T],
gap: I32
}
// Multiple constraints
pub trait Renderable { fn render(self) -> Boolean }
pub trait Clickable { fn click(self) -> Boolean }
pub struct Widget<T: Renderable + Clickable> {
component: T
}
Generic Traits
pub trait Collection<T> {
items: [T]
}
pub trait Comparable<T> {
fn compare(self, other: T) -> I32
}
Generic Enums
pub enum Result<T, E> {
ok(value: T),
error(err: E)
}
pub enum Option<T> {
some(value: T),
none
}
Generic Instantiation
pub struct Box<T> { value: T }
pub struct Pair<A, B> { first: A, second: B }
pub enum Result<T, E> { ok(value: T), error(err: E) }
pub enum Option<T> { some(value: T), none }
// With explicit type arguments
pub let string_box = Box<String>(value: "hello")
pub let number_box = Box<I32>(value: 42)
pub let pair = Pair<I32, Boolean>(first: 42, second: true)
// Type inference (when inferrable)
pub let inferred_box = Box(value: "inferred as String")
pub let inferred_pair = Pair(first: 10, second: true)
// Generic enums
pub let success: Result<String, I32> = .ok(value: "success")
pub let failure: Result<String, I32> = .error(err: 404)
pub let maybe: Option<I32> = .some(value: 42)
pub let nothing: Option<I32> = .none
// A variant path takes the type arguments too
pub let also_nothing = Option<I32>.none
An instantiation with no <...> takes its type arguments from the
type that its position expects: a let annotation, a parameter, a
return type, a field, an element, or the branches of an if or a
match. Only when the position expects nothing do the arguments come
from the fields or the payload.
pub struct Box<T> { value: T }
pub fn run_checks() {
let b: Box<I32?> = Box(value: 1) // a Box<I32?>, not a Box<I32>
let xs: [Box<I32?>] = [Box(value: 2), Box(value: nil)]
assert(condition: b.value != nil)
assert(condition: xs.len() == 2)
}
Two instances of one generic type are two types. A value of type
Box<I32> does not fit where a Box<I32?> goes, even though an I32
fits where an I32? goes:
pub struct Box<T> { value: T }
pub fn run_checks() {
let a = Box(value: 1) // a Box<I32>
let b: Box<I32?> = a // error: TypeMismatch
}
A type parameter that only the return type mentions takes its type
from the position of the call: a let annotation, a parameter, a
return type, a field, a payload, an assignment, an element, or the
branches of an if or a match. The call is then the same as a call
with <...>:
pub struct Meters { v: I64 }
impl From<Literal<I64>> for Meters {
fn from(value: Literal<I64>) -> Meters { Meters(v: value.value) }
}
fn zero<T: From<Literal<I64>>>() -> T { 0 }
pub let d: Decimal = zero() // zero<Decimal>()
pub let m: Meters = zero() // zero<Meters>()
pub let o: Meters? = zero() // zero<Meters>(): a value fits its optional
A return type of T in a position that expects X? gives T the
type X when X meets the bounds of T, and else X?. With no
expected type, let d = zero() is the error
UninferableMethodTypeParameter (E147).
Generic Methods
A method can declare its own type parameters, after its name. They sit beside the type parameters of the impl block:
pub struct Box<T> {
value: T
}
impl Box<T> {
fn map<U>(self, f: (T) -> U) -> Box<U> {
Box(value: f(self.value))
}
}
pub let flag = Box(value: 3).map(f: (v) -> v > 2) // Box<Boolean>
A type parameter that only the return type mentions takes its type from the position of the call, on a value and on a type:
pub struct Meters { v: I64 }
impl From<Literal<I64>> for Meters {
fn from(value: Literal<I64>) -> Meters { Meters(v: value.value) }
}
pub struct Scale { n: I64 }
impl Scale {
fn unit<T: From<Literal<I64>>>() -> T { 1 }
fn size<T: From<Literal<I64>>>(self) -> T { 2 }
}
pub let u: Meters = Scale.unit() // unit with T = Meters
pub let d: Decimal = Scale(n: 1).size() // size with T = Decimal
Rules:
- A call may write the method's type arguments after its name, as for
a function:
s.size<Decimal>(). A written type must meet the bounds (GenericConstraintViolation), the call must write one for each type parameter (GenericArityMismatch), and an argument must agree with it (TypeMismatch) - With no
<...>, the arguments give each type parameter of the method its type, as for a free function. A type parameter that no argument binds takes its type from the position of the call, through the return type. So each one must appear in the type of a parameter with no default, or in the return type (E147) - A call may leave out a parameter with a default, and then that
parameter gives no type. A call whose arguments and position give a
type parameter no type is the error E147 at the call:
let x = s.convert()forfn convert<T>(self) -> T - The type parameters of the impl block come from the receiver, never from the position of the call
- A closure argument can give a type parameter its type through its
return type:
Uabove takes the type thatfanswers. A closure parameter with no type cannot: infn apply<U>(self, f: (U) -> I32), the callb.apply(f: (x) -> 1)givesxno type (E145). Write it:(x: String) -> 1 - A method type parameter cannot repeat a name of its impl block, or of the type when the impl block names none (E084)
- A trait method may declare type parameters of its own too. See Traits
- A type parameter hides a type with the same name inside its definition
Type Constraints
pub trait Named { name: String }
pub trait Layout { width: I32 }
pub trait Renderable { fn render(self) -> Boolean }
pub trait Clickable { fn click(self) -> Boolean }
// Single constraint
pub struct Wrapper<T: Named> {
item: T
}
// Multiple constraints
pub struct Interactive<T: Renderable + Clickable> {
component: T
}
// Constraint on trait field
pub trait Container<T: Layout> {
items: [T]
}
Rules:
- Type parameters use
<T>,<A, B>, etc. - Constraints use
:syntax:<T: Constraint> - Multiple constraints use
+:<T: A + B> - Type arguments must match parameter count (arity). A wrong count,
or type arguments on a type that takes none, is the error
GenericArityMismatch - A type parameter is visible only in its own definition. A use
outside it is the error
OutOfScopeTypeParameter - Type inference works when types can be determined. Two arguments
that give one type parameter two types are a
TypeMismatch - A free function gives each type parameter its type through
<...>at the call, through an argument, or through the type that the position of the call expects. The arguments give their types first. The position gives a type only to a parameter that the arguments leave free, through the return type of the function fn make<T>() -> I32needsmake<I32>(): a baremake()is the errorUninferableMethodTypeParameter(E147). Neither an argument nor the return type givesTa type- The type arguments at the call give each type parameter its type
in the function, and a copy of the function for those types.
make<I32>()andmake<String>()are two calls of two copies - Constraints must reference existing traits: an unknown name is the
error
UndefinedTrait, and a struct is the errorNotATrait - A bound can name the type parameter that it bounds:
<T: Comparable<T>>. WithT = N, the bound isComparable<N> - A body that compares values of a type parameter with
==or!=needs an equatable type argument. A closure, and a type that holds one, is not equatable:same(a: f, b: g)on two closures is the errorNotEquatableTypeArgument(E177). This holds through a generic caller that gives its own type parameter tosame - A constraint holds wherever the type argument comes from — written at
the call site (
total<Square>(item: s)) or inferred from the argument (total(item: s))
pub trait Shape { fn area(self) -> I32 }
pub struct Square { side: I32 }
pub struct Plain { n: I32 }
impl Shape for Square {
fn area(self) -> I32 { self.side * self.side }
}
fn total<T: Shape>(item: T) -> I32 { item.area() }
let a: I32 = total(item: Square(side: 3)) // ok
let b: I32 = total(item: Plain(n: 1)) // error E081: Plain is not a Shape
fn same<T>(a: T, b: T) -> Boolean { a == b }
let a: Boolean = same(a: 1, b: 1) // ok
let b: Boolean = same(a: (x: I32) -> x, b: (x: I32) -> x) // error E177
Monomorphisation
The MonomorphisePass clones generic definitions per unique
argument tuple after parsing: see
Built-in Passes / MonomorphisePass.
The set of clones must be finite. A generic function that calls
itself with a larger type argument than its own, such as Box<T> for
T, would need a new clone at each level. The pass stops at 32 levels
with the error InstantiationDepthExceeded (E148). A written type that
nests its type arguments more than 32 levels deep gets the same error.
struct Box<T> { value: T }
fn grow<T>(x: T, n: I32) -> I32 {
if n <= 0 { 0 } else { 1 + grow(x: Box(value: x), n: n - 1) }
}
pub fn probe() -> I32 { grow(x: 1, n: 3) }
A generic function that calls itself must call itself with the same type arguments. Wrap the growing value in a non-generic type, or give it a fixed type.