Type declarations
Type declarations are used for defining custom types that improve readability, reusability, and structure of the code. They can represent records, variants, objects, or parameterized (generic) types. Motoko enforces productivity and non-expansiveness in type declarations to ensure well-formed, valid types.
Defining a type
Section titled “Defining a type”The type keyword assigns a name to a type and makes it reusable.
// An alias for Nattype Age = Nat;
// An alias for Texttype Username = Text;These types can then be used in function definitions.
func greet(name : Username, age : Age) : Text { "Hello, " # name # "! You are " # Nat.toText(age) # " years old."}They can also be used in other type definitions:
type Person = { username : Username; age : Age};Given a similar type User:
type User = { age : Nat; username : Text };Structural typing means that the types User and Person are interchangeable because their definitions are equivalent (after expanding all definitions). In other languages, User and Person might be considered incompatible types.
Record types
Section titled “Record types”In Motoko, a type can define a structured record with labeled fields. Each field has a specific type, and you can access them using dot notation. Records are useful for organizing related data clearly and safely.
// A reusable recordtype Ghost = { firstName : Text; lastName : Text; age : Nat;};
// An instance of Ghostlet motoko : Ghost = { firstName = "Motoko"; lastName = "Sentinels"; age = 30;};Variant types
Section titled “Variant types”A type can also define variants, which represent different possible states or alternatives. Variants allow a value to be one of several labeled options, making it easy to handle data that can take multiple forms.
// Allows only one of its variants at a time.type Status = { #Active; #Inactive;
// Carries an additional Text value. #Banned : Text;};
let userStatus : Status = #Active;let bannedUser : Status = #Banned("Violation of rules");Parameterized (generic) types
Section titled “Parameterized (generic) types”Type declarations can be parameterized, allowing them to work flexibly with multiple types while ensuring type safety. This lets you create generic and reusable type definitions that adapt to different data types as needed.
// `Box<T> is a generic type where T represents any type.type Box<T> = { value: T;};
// numberBox stores a Nat and textBox stores a Text.let numberBox : Box<Nat> = { value = 42 };let textBox : Box<Text> = { value = "Hello" };In Motoko, all types defined within the same block can refer to each other, allowing mutually recursive type definitions.
For example, you can split the list type above into lists with even and odd numbers of elements:
type EvenList<T> = ?(T, OddList<T>);type OddList<T> = (T, EvenList<T>);Recursive types and productivity
Section titled “Recursive types and productivity”Motoko allows recursive type definitions as long as they are productive. This means that any recursion in a type must pass through a constructor (such as an option (?), a variant, or a record field) before referring back to itself.
Productive recursive type example
Section titled “Productive recursive type example”type List<T> = ?(T, List<T>);List<T> defines a linked list where each node holds a value of type T and points to either another List<T> or null to mark the end. Since the recursion passes through a constructor (?), this type is productive and accepted by the compiler.
Non-productive recursive type example
Section titled “Non-productive recursive type example”type C = C; // This definition immediately refers to itselfThis type definition is considered non-productive because it is too cyclic. It never expands to a concrete type. As a result, the compiler rejects it to prevent infinite expansion or ill-formed types.
Expansiveness in type definitions
Section titled “Expansiveness in type definitions”Motoko enforces that type definitions do not expand into ever-growing sets of types. This property, called non-expansiveness, guarantees it is always possible to decide whether one type is a subtype of another. Without it, compilation of code may never finish.
Non-expansive
Section titled “Non-expansive”// Expands without introducing an infinite set of new instances of `List<_>`.type List<T> = ?(T, List<T>);Expansive
Section titled “Expansive”// Expands by introducing infinitely many instances of `Seq<_>`: `Seq<[T]>`, `Seq<[[T]]>`, `Seq<[[[T]]]>`, ....// This is expansive and not allowed.type Seq<T> = ?(T, Seq<[T]>);