Skip to main content

Imports

Imports must appear at the top of the file:
import "another-file"

// Symbols from `another-file.tolk` become available in this file.
In most workflows, the IDE adds imports automatically. For example, when selecting an item from auto-completion. The entire file is imported. There are no modules or exports; all symbols must have unique names within the project.

Structures

A struct Point holding two 8-bit integers:
struct Point {
    x: int8
    y: int8
}

fun demo() {
    // create an object
    val p1: Point = { x: 10, y: 20 };

    // the same, type of p2 is auto-inferred
    val p2 = Point { x: 10, y: 20 };
}
  • Methods are declared as fun Point.method(self).
  • Fields can use any types: numeric, cell, union, and others.
  • Fields can define default values: x: int8 = 0.
  • Fields can be private and readonly.
  • Structs can be generic: struct Wrapper<T> { ... }.
If all fields are serializable, a struct can be automatically serialized:
// makes a cell containing hex "0A14"
val c = p1.toCell();
// back to { x: 10, y: 20 }
val p3 = Point.fromCell(c);

Functions

A function that returns the sum of two integers:
fun sum(a: int, b: int): int {
    return a + b;
}
  • Parameter types are mandatory.
  • The return type can be omitted: it is auto-inferred.
  • Parameters can define default values: fun f(b: int = 0)
  • Statements in a block are separated by semicolons ;.
  • Generic functions are supported: fun f<T>(value: T) { ... }
  • Assembler functions are supported: fun f(...): int asm "..."

Methods

A function declared as fun <receiver>.name(...) is a method.
  • If the first parameter is self, it’s an instance method.
  • If the first parameter is not self, it’s a static method.
// `self` — instance method (invoked on a value)
fun Point.sumCoords(self) {
    return sum(self.x, self.y);
}

// not `self` — static method
fun Point.createZero(): Point {
    return { x: 0, y: 0 };
}

fun demo() {
    val p = Point.createZero();    // { 0, 0 }
    return p.sumCoords();          // 0
}
By default, self is immutable; mutate self allows modifying the object. Methods can be declared for any type, including primitives:
fun int.isNegative(self) {
    return self < 0
}

Variables

Within functions, variables are declared with val or var keywords. The val keyword declares an immutable variable that can be assigned only once:
val coeff = 5;
// cannot change its value, `coeff += 1` is an error
The var keyword declares a variable that can be reassigned:
var x = 5;
x += 1;      // now 6
A variable’s type can be specified after its name:
var x: int8 = 5;
Declaring variables at the top level, outside functions, is supported using the global keyword.

Constants

Constants can be declared only at the top level, not inside functions:
const ONE = 1
const MAX_AMOUNT = ton("0.05")
const ADMIN_ADDRESS = address("EQ...")
To group integer constants, enums are useful.

Value semantics

Tolk follows value semantics: assignments create independent copies, and function calls do not mutate arguments unless explicitly specified.
var a = Point { x: 1, y: 2 };
var b = a;   // `b` is a copy
b.x = 99;    // `a.x` remains 1
someFn(a);   // pass a copy; `a` will not change

// but there can be mutating functions, called this way:
anotherFn(mutate a);

Semicolons

  • Semicolons are optional at the top level, after imports, aliases, etc.
  • Semicolons are required between statements in a function.
  • After the last statement in a block, a semicolon is optional.
// optional at the top-level
const ONE = 1
type UserId = int

// required inside functions
fun demo() {
    val x = 5;
    val y = 6;
    return x + y    // optional after the last statement
}

Comments

Tolk supports single-line or end-of-line and multi-line or block comments:
// This is a single-line comment

/* This is a block comment
   across multiple lines. */

const TWO = 1 /* + 100 */ + 1    // 2

Conditional operators

In conditions, if is a statement. else if and else blocks are optional.
fun sortNumbers(a: int, b: int) {
    if (a > b) {
        return (b, a)
    } else {
        return (a, b)
    }
}
A ternary operator is also available:
val sign = a > 0 ? 1 : a < 0 ? -1 : 0;

Union types and matching

Union types allow a variable to hold one of possible types. They are typically handled by match:
fun processValue(value: int | slice) {
    match (value) {
        int => {
            value * 2
        }
        slice => {
            value.loadUint(8)
        }
    }
}
Alternatively, test a union with is or !is operators:
fun processValue(value: int | slice) {
    if (value is slice) {
        // call methods for `slice`
        return;
    }
    // value is `int`
    return value * 2;
}
Union types are commonly used when handling incoming messages.

While loop

Tolk does not have a for loop; use while loop for repeated execution.
while (i > 0) {
    // ...
    i -= 1;
}

Assert and throw

The try-catch statement is supported for exceptions, although it is not commonly used in contracts.
const ERROR_NO_BALANCE = 403;

// in some function
throw ERROR_NO_BALANCE;

// or conditional throw
assert (balance > 0) throw ERROR_NO_BALANCE;

Iterate over a map

To iterate, maps can be used:
fun iterateOverMap(m: map<int32, Point>) {
    var r = m.findFirst();
    while (r.isFound) {
        // ...
        r = m.iterateNext(r);
    }
}

Send a message to another contract

To construct and send a message, a message body is typically represented by a structure. For example, RequestedInfo:
val reply = createMessage({
    bounce: BounceMode.NoBounce,
    value: ton("0.05"),
    dest: someAddress,
    body: RequestedInfo { ... }
});
reply.send(SEND_MODE_REGULAR);

Contract getters

Contract getters or get-methods are declared with get fun:
get fun currentOwner() {
    val storage = lazy Storage.load();
    return storage.ownerAddress;
}