- 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:
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.