Skip to main content

Language guide

Table of contents

  1. Basic syntax
  2. Functions
  3. Variables and types
  4. Control flow
  5. Type system
  6. Collections
  7. Error handling
  8. Structures
  9. Methods
  10. Imports and modules
  11. Advanced features
  12. Standard library

Basic syntax

Comments

Tolk uses traditional C-style syntax for comments:

// Single-line comment

/*
Multi-line comment
can span multiple lines
*/

Identifiers

Identifiers must start with [a-zA-Z$_] — that is, a letter, underscore, or dollar sign — and may continue with characters from [a-zA-Z0-9$_].

As in most programming languages, camelCase is the preferred naming convention.

var justSomeVariable = 123;

Functions

Function declaration

Use the fun keyword with TypeScript-like syntax:

fun functionName(param1: type1, param2: type2): returnType {
// function body
}

Examples:

fun parseData(cs: slice): cell { }
fun loadStorage(): (cell, int) { }
fun main() { ... }

If the return type is omitted, it's auto-inferred.

Generic functions

Tolk supports generic functions:

fun swap<T1, T2>(a: T1, b: T2): (T2, T1) {
return (b, a);
}

Default parameters

Function parameters can have default values:

fun increment(x: int, by: int = 1): int {
return x + by;
}

GET methods

Contract getters use the get fun syntax:

get fun seqno(): int {
return 1;
}

Variables and types

Variable declaration

Declare variables with var for mutable variables and val for immutable ones:

var mutableVar: int = 10;
val immutableVar: int = 20;

Type annotations are optional for local variables:

var i = 10; // int inferred
var b = beginCell(); // builder inferred

Variable scope

Variables cannot be redeclared within the same scope:

var a = 10;
var a = 20; // Error! Use: a = 20;

if (true) {
var a = 30; // OK, different scope
}

Control flow

Conditional statements

if (condition) {
// code
} else if (anotherCondition) {
// code
} else {
// code
}

Loops

While loop

while (condition) {
// code
}

Do-while loop

do {
// code
} while (condition);

Repeat loop

repeat (10) {
// body
}

Type system

Basic types

  • int - integer; fixed-width variants like int32 and uint64 are also supported
  • bool - boolean (true/false). Note: in TVM, true is represented as -1, not 1
  • cell - a TVM cell
  • slice - a TVM slice; you can also use bitsN. (e.g. bits512 for storing a signature)
  • builder - a TVM builder
  • address - blockchain address
  • coins - Toncoin or coin amounts
  • void - no return value
  • never - function never returns (always throws an exception)

Fixed-width integers

var smallInt: int32 = 42;
var bigInt: uint64 = 1000000;

Boolean type

The Boolean type is distinct from integers:

var valid: bool = true;
var result: bool = (x > 0);

if (valid) { // accepts bool
// code
}

// Cast to int if needed
var intValue = valid as int; // -1 for true, 0 for false

Nullable types

Nullable types are denoted by ?:

var maybeInt: int? = null;
var maybeCell: cell? = someCell;

if (maybeInt != null) {
// Smart cast: maybeInt is now int
var result = maybeInt + 5;
}

Union types

Union types allow a value to have multiple possible types. Typically, you handle them using match by type:

fun processValue(value: int | slice) {
match (value) {
int => {
// Got integer
}
slice => {
// Got slice
}
}
}

Alternatively, you can test a union value using the is or !is operators:

fun processValue(value: int | slice) {
if (value is slice) {
// call methods for slice
return;
}
// value is int
return value * 2;
}

Tuple types

Tuples with explicit types:

var data: [int, slice, bool] = [42, mySlice, true];

Tensor types

Tensors are similar to tuples but stored on the stack:

var coords: (int, int) = (10, 20);
var x = coords.0; // Access first element
var y = coords.1; // Access second element

Type aliases

Create aliases to improve code clarity:

type UserId = int32
type MaybeOwnerHash = bits256?

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

Address type

A dedicated type for blockchain addresses:

val addr = address("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF");

if (addr.isInternal()) {
var workchain = addr.getWorkchain();
}

Tensors

Indexed access

Access elements using dot . notation:

var t = (5, someSlice, someBuilder);
t.0 = 10; // Modify first element
t.1; // Access second element

Tuples

var t = [5, someSlice, someBuilder];
t.0 = 10; // asm "SETINDEX"
var first = t.0; // asm "INDEX"

Error handling

Throw and assert

Simplified error handling:

throw 404; // Throw exception with code
throw (404, "Not found"); // Throw with data

assert (condition) throw 404;
assert (!condition) throw 404;

Try-catch

try {
riskyOperation();
} catch (excNo, arg) {
// Handle exception
}

Structures

Struct declaration

struct Point {
x: int
y: int
}

Creating objects

var p: Point = { x: 10, y: 20 };
var p2 = Point { x: 5, y: 15 };

Default values

struct Config {
timeout: int = 3600
enabled: bool = true
}

var config: Config = {}; // Uses defaults

Generic structures

struct Container<T> {
value: T
isEmpty: bool
}

var intContainer: Container<int> = { value: 42, isEmpty: false };

Methods

Instance methods

Methods are implemented as extension functions that accept self:

fun Point.distanceFromOrigin(self): int {
return sqrt(self.x * self.x + self.y * self.y);
}

Mutating methods

Use the mutate keyword for methods that modify the receiver:

fun Point.moveBy(mutate self, dx: int, dy: int) {
self.x += dx;
self.y += dy;
}

Methods for any type

fun int.isZero(self) {
return self == 0;
}

fun T.copy(self): T {
return self;
}

Chaining methods

Methods can return self to enable method chaining:

fun builder.storeInt32(mutate self, value: int32): self {
return self.storeInt(value, 32);
}

Imports and modules

Import syntax

import "another";
import "@stdlib/tvm-dicts";

Standard library

Common functions are available by default:

// Common functions always available
var time = blockchain.logicalTime();

Some functions require importing specific modules (your IDE will suggest them):

import "@stdlib/tvm-dicts";

var dict = createEmptyDict();

Advanced features

TVM assembler functions

You can implement functions directly in TVM assembler:

@pure
fun third<X>(t: tuple): X
asm "THIRD"

Function attributes

Functions can have attributes using the @ syntax:

@inline
fun fastFunction() {}

@inline_ref
fun load_data() {}

@deprecated
fun oldFunction() {}

@method_id(1666)
fun afterCodeUpgrade(oldCode: continuation) {}

Trailing commas

Tolk supports trailing commas in tensors, tuples, function calls, and parameters:

var items = (
totalSupply,
verifiedCode,
validatorsList,
);

Optional semicolons

Semicolons are optional for the last statement in a block:

fun f() {
doSomething();
return result // Valid without semicolon
}

Semicolons are also optional for top-level declarations such as constants, type aliases, and struct fields.

Compile-time functions

String-processing functions execute at compile time:

const BASIC_ADDR = address("EQDKbjIcfM6ezt8KjKJJLshZJJSqX7XOA4ff-W72r5gqPrHF");
const HASH = stringSha256_32("transfer(slice, int)");

Available compile-time functions include: stringCrc32, stringCrc16, stringSha256, stringSha256_32, stringHexToSlice, and stringToBase256.

Toncoin amounts

Use human-readable Toncoin amounts:

val cost = ton("0.05"); // 50,000,000 nanotons
const ONE_TON = ton("1");

Smart casts

Automatic type narrowing:

if (value != null) {
// value is automatically cast from T? to T
value.someMethod();
}

Non-null assertion

Use ! when you are sure a value is not null:

fun processCell(maybeCell: cell?) {
if (hasCell) {
processCell(maybeCell!); // bypass nullability check
}
}

Auto-packing

Structures can be automatically packed to and unpacked from cells:

struct Point {
x: int8
y: int8
}

var point: Point = { x: 10, y: 20 };
var cell = point.toCell(); // Auto-pack
var restored = Point.fromCell(cell); // Auto-unpack

Deep dive: Auto-packing.

"Lazy loading" from cells: unpack only requested fields

val st = lazy Storage.load();
// the compiler skips everything and loads only what you access
return st.publicKey;

Deep dive: Lazy loading.

Universal message composition

Create messages using a high-level syntax:

val reply = createMessage({
bounce: false,
value: ton("0.05"),
dest: senderAddress,
body: RequestedInfo { ... }
});
reply.send(SEND_MODE_REGULAR);

Deep dive: Sending messages.

Was this article useful?