Skip to main content
Tolk supports type aliases, similar to TypeScript and Rust. An alias creates a new name for an existing type and remains fully interchangeable with it.
type UserId = int32
type MaybeOwnerHash = bytes32?

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

Alias interchangeability

Aliases are interchangeable with underlying types. UserId and int32 from the above example are fully equivalent:
  • id + 1 is valid and produces int;
  • someF(id) is valid if someF accepts int32 or int;
  • methods for int32 can be called on UserId values and vice versa; and also for int, since int32 is assignable to int;
  • a union UserId | int32 is redundant and is equivalent to int32.
fun demo() {
    var id: UserId = 1;       // ok
    var num: int = id;        // ok
    var h = calcHash(id);
    if (h != null) {
        h as slice;           // bytes32 as slice
    }
}
To obtain a “strict alias”, which defines a distinct type, use a struct with one field:
struct UniqueId {
    value: int32
}
Such a struct has no overhead over int32, but it becomes a distinct type with its own methods and semantics.

Distinct alias types

Two aliases with the same underlying type are considered distinct. If two aliases share the same underlying type:
type AssetsDict = dict
type BalanceDict = dict
Then, they are not assignable to each other. This allows them to define methods with identical names:
fun AssetsDict.validate(self) { /* ... */ }
fun BalanceDict.validate(self) { /* ... */ }

fun demo(a: AssetsDict, b: BalanceDict) {
    a.validate();      // ok, method 1
    b.validate();      // ok, method 2
    a = b;             // error, can not assign
}
This behavior is similar to intN types: int32 is assignable to int, and int64 is assignable to int, but int32 and int64 are not assignable to each other. Assignment can be done with an explicit cast. For example, b as AssetsDict.

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 overloaded 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
}