Skip to main content
  • Use val for immutable variables and var for mutable variables.
  • Use const or global outside functions.

Declaration

  • val declares a variable that is assigned exactly once:
    fun demo() {
        val coeff = 5;
        // cannot change its value, `coeff += 1` is an error
    }
    
  • var declares a variable that can be reassigned:
    fun demo() {
        var x = 5;
        x += 1;      // now 6
    }
    

Explicit types

A variable may declare its type explicitly. If the type is not specified, it is inferred from the initial assignment:
fun demo() {
    var x = 5;         // inferred `int`
    x = null;          // error, cannot assign

    var y: int? = 5;   // specified nullable
    y = null;          // ok
}
Explicit types also can be used with structures:
fun demo() {
    var p1: Point = { x: 10, y: 20 };

    // without an explicit type, use `Point { ... }`
    var p2 = Point { x: 10, y: 20 };
}

Unassigned variables

A variable may be declared without an initial value, but it must be assigned before its first use:
fun demo(mode: int) {
    var result: int;  // not assigned at declaration

    if (mode == MODE_SLOW) {
        result = doSlowCalc();
    } else if (mode == MODE_FAST) {
        result = doFastCalc();
    } else {
        throw ERR_INVALID_MODE;
    }
    return result;   // ok, it's definitely assigned
}

Multiple variables

Declaring multiple variables at once is tensor destructuring:
fun demo() {
    var (a, b) = (1, "");

    // with explicit types
    var (c: int, d: slice) = (1, "");
}

Nested scope

A block { ... } introduces a nested scope:
fun demo() {
    val x = 10;
    if (smth) {
        val x = 50;  // this is a different `x`
    }
    // x is 10
}

Function parameters

Function parameters behave the same as local variables. They can be reassigned, but the reassignment does not affect the caller’s state:
fun analyze(userId: int?) {
    if (userId == null) {
        userId = DEFAULT_ID;
    }
    // ...
}

fun demo() {
    var id = null as int?;
    analyze(id);
    // id remains `null`
}
To make updates to userId visible in demo, declare the parameter with mutate: mutate userId.

Constants

Global-scope constants are declared with const outside functions:
const SLEEP_TIME_SEC = 5
The right-hand side must be a constant expression: numbers, const literals, compile-time functions, etc.
// ok
const FLAG_JANUARY = 1 << 10
const OP_TRANSFER = stringCrc32("transfer")

// error: not a constant expression
const CUR_TIME = blockchain.now()
The type is inferred unless explicitly specified:
const MODE_NORMAL: uint32 = 0
Constants are not limited to integers:
// type `address`
const ADMIN_ADDR = address("UQ...")

// type `coins`
const MINIMAL_COST = ton("0.05")

// even objects with constant fields
const ZERO_POINT: Point = { x: 0, y: 0 }
To calculate CRC32 and similar values at compile time, use stringCrc32("...") and similar. To group integer constants, use enums.

Global variables

Use the global keyword to declare variables outside functions:
global runtimeCalls: tuple
A global must have an explicit type and cannot be initialized at the point of declaration. Initialization is done manually at some point in a program. A contract has several entry points, such as get fun, onInternalMessage, and others. A global must be initialized along the execution path where it is required.
global runtimeCalls: tuple

fun execute() {
    runtimeCalls.push("start execute");
    // ...
}

get fun devTrace() {
    runtimeCalls = createEmptyTuple();   // initialize
    val result = execute();
    return (result, runtimeCalls);
}
In Tolk, avoid globals when possible. Use auto-serialization and lazy loading.
Use globals with care. Until initialized, a global holds TVM NULL, and any invalid access leads to a runtime failure.