Skip to content

Types

These types come from the language itself, so they need no import.

Each of these is declared in the prelude.

TypePrelude section
int int8 int16 int32 int64Numerics
uint uint8 uint16 uint32 uint64 uintptrNumerics
byte runeNumerics
float32 float64 complex64 complex128Numerics
boolBooleans
stringStrings
Option<T>Option
Result<T, E>Result
Partial<T, E>Partial
Slice<T>Slice
Array<T, N>Array
Map<K, V>Map
Ref<T>Ref
NeverNever

Tuples hold 2 to 5 values, which may have different types. Access elements by position.

let pair = (42, "hello")
let first = pair.042
let second = pair.1"hello"
let triple = (1, true, "three")
let (a, b, c) = triplea is 1, b is true, c is "three"

For more than 5 elements, use a struct with named fields.

The implicit return type of functions that return no value. Written as ().

fn greet(name: string) {implies -> ()
fmt.Println(f"hello, {name}")
}

A type annotation names a type the compiler cannot work out on its own. Write it after : on a binding, parameter, or field, and after -> for a return type.

let empty = []error: element type cannot be inferred
let empty: Slice<int> = []annotation decides element type

Most types are inferred from the value, so annotations are the exception.

A type parameter stands for a type supplied later, when the declaration is used. Functions, methods, structs, enums, and aliases all take type parameters, written in angle brackets after the name.

fn first<T>(xs: Slice<T>) -> Option<T>
struct Pair<A, B> { left: A, right: B }
enum Tree<T> { Leaf(T), Node(Ref<Tree<T>>, Ref<Tree<T>>) }

Supply them at the point of use, or leave them out where the context decides:

let scores = Map.new<string, int>()supplied
let names = ["Alice", "Bob"]inferred as Slice<string>

A type parameter can carry a bound T: Constraint that constrains which types may instantiate it. Any interface serves as a constraint, and the prelude supplies two: Comparable and Ordered.

interface Display {
fn to_string() -> string
}
T must satisfy Display
fn print_value<T: Display>(value: T) {
fmt.Println(value.to_string())
}

To give a type parameter multiple bounds:

fn render<T: Display + Shape>(value: T) -> string
T must satisfy both
Display and Shape

To give type parameters individual bounds:

fn label<T: Display, U: Ordered>(value: T, rank: U) -> string
T must satisfy Display
U must satisfy Ordered

To alias a type:

type UserId = int
type Handler = fn(Request) -> Response
type StringMap<V> = Map<string, V>

Type aliases are transparent, i.e. alternative names for the same type.

type UserId = int
let id: UserId = 42
let n: int = idUserId is just int

For distinct types, use a tuple struct:

struct UserId(int)
struct OrderId(int)
let user = UserId(1)
let order = OrderId(2)
let n: int = usererror: expected int, found UserId

Types never convert implicitly, not even between numeric types. The as operator converts between types explicitly.

let a: int = 1
let b: int64 = 2
let c = a + berror: cannot add int and int64
let c = a + (b as int)
explicit conversion

Numeric conversions:

let x: int = 42
let y = x as float64
let z = x as int8

Conversions of string to bytes or runes:

let s = "hello"
let bytes = s as Slice<byte>
let runes = s as Slice<rune>
let back = bytes as string

Conversions between incompatible types are disallowed.

let b = true
let n = b as interror: cannot convert bool to int