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 + 1is valid and producesint;someF(id)is valid whensomeFacceptsint32orint;- methods of
int32andintcan be called on aUserId.
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 // errorTo 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 errorGeneric 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
}