TON DocsTON Docs
OnboardingNodesApplicationsAPIsContractsSmart contractsTolkTolk languageTVMTON Virtual MachineFoundationsBlockchain foundations

Type aliases

Tolk supports type aliases. An alias gives an existing type another name. Values of the alias and of the underlying type are assignable in both directions, because they share one runtime representation. Methods, match, and is treat the alias as a subtype of that type: operations of the underlying type are available on the alias, and operations declared on the alias stay on the alias.

type UserId = int32
type MaybeOwnerHash = bits256?

fun calcHash(id: UserId): MaybeOwnerHash {
    // ...
}

Assignment

UserId and int32 from the example above can be assigned to each other. Operators and functions of the underlying type accept the alias:

  • id + 1 is valid and produces int;
  • someF(id) is valid when someF accepts int32 or int;
  • methods of int32 and int can be called on a UserId.
fun demo() {
    var id: UserId = 1;       // ok
    var num: int = id;        // ok
    var back: UserId = num;   // ok
    var h = calcHash(id);
    if (h != null) {
        h as slice;           // bits256 as slice
    }
}

A union of an alias and its underlying type is invalid. Both variants have the same runtime representation:

type MyInt = int
type Both = int | MyInt    // error

To get a distinct type with its own stack identity, use a struct with one field:

struct UniqueId {
    value: int32
}

Such a struct has no overhead over int32. It is a separate type, with its own methods and serialization.

Methods belong to the alias

A method declared on an alias is available on that alias. It is unavailable on the underlying type until the value is cast to the alias.

type MerkleProofCell = cell

fun MerkleProofCell.validate(self) { /* ... */ }

fun demo(c: cell, proof: MerkleProofCell) {
    proof.hash();             // ok, methods of `cell` are available on the alias
    proof.validate();         // ok
    c.validate();             // error
    (c as MerkleProofCell).validate();
}

The relation is structural. A method of array<cell> is available on array<MerkleProofCell>. A method of array<MerkleProofCell> is available only on that type:

fun array<cell>.hashAll(self) { /* ... */ }
fun array<MerkleProofCell>.validateAll(self) { /* ... */ }

Sibling aliases

Two aliases of one underlying type are independent subtypes. They stay assignable to each other and to the underlying type. A method declared on one alias is unavailable on the other alias and on the underlying type.

type AssetsDict = dict
type BalanceDict = dict

fun AssetsDict.validate(self) { /* ... */ }
fun BalanceDict.validate(self) { /* ... */ }

fun demo(a: AssetsDict, b: BalanceDict, plain: dict) {
    a.validate();       // AssetsDict.validate
    b.validate();       // BalanceDict.validate
    plain.validate();   // error: no method `validate` for `dict`

    a = b;              // ok, both are `dict` at runtime
    a = plain;          // ok
}

match and is

match and is follow the same direction as methods. An alias can be tested as its underlying type. The underlying type cannot be tested as the alias.

fun demoAlias(m: MerkleProofCell | int) {
    match (m) {
        cell => { /* upcast, ok */ }
        int => { /* ... */ }
    }
}
// but not vice versa!
// match (cell) { MerkleProofCell => ... } is an error

Generic type aliases

Type aliases can be generic.

type Wrapper<T> = Nothing | Container<T>

Stack layout and serialization

A type alias is identical to its underlying type in stack layout and serialization.

Serialization behavior can be overridden by defining custom serializers. This is useful when binary encoding cannot be expressed using existing types.

type MyString = slice

fun MyString.packToBuilder(self, mutate b: builder) {
    // custom logic
}

fun MyString.unpackFromSlice(mutate s: slice) {
    // custom logic
}

On this page