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

// Single-line comment

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

Identifiers

Identifiers can start with [a-zA-Z$_] and continue with [a-zA-Z0-9$_], like in most languages, camelCase preferred.

var justSomeVariable = 123;

Functions

Function declaration

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() { ... }

When return type 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

Parameters can have default values:

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

GET methods

Contract getters — with get fun:

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

Variables and types

Variable declaration

Variables are declared with var (mutable) or val (immutable):

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 in 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, you can also use fixed-width integers e.g. int32, uint64
  • bool - boolean (true/false) (caution! TRUE in TVM is -1, not 1)
  • cell - TVM cell
  • slice - TVM slice, you can also use bitsN. (e.g. bits512 for storing signature)
  • builder - TVM builder
  • address - blockchain address
  • coins - Toncoin/coins amounts
  • void - no return value
  • never - never returns (always throws)

Fixed-width integers

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

Boolean type

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 with ?:

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 multiple possible types. They are typically handled with match by type:

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

Alternatively, a union can be tested via 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

Typed tuples:

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

Tensor types

Tensors (like tuples but on stack):

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

Type aliases

Create type aliases for clarity:

type UserId = int32
type MaybeOwnerHash = bits256?

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

Address type

Dedicated type for blockchain addresses:

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

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

Tensors

Indexed access

Access elements with 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 extension functions accepting self:

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

Mutating methods

Use mutate 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 for 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();

While specific require importing some module (IDE suggests you):

import "@stdlib/tvm-dicts";

var dict = createEmptyDict();

Advanced features

TVM assembler functions

You can implement functions in TVM Assembler:

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

Function attributes

Functions can have attributes using @ 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
}

Also, semicolons are optional for top-level declarations: constants, type aliases, struct fields.

Compile-time functions

String processing functions work at compile time:

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

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

Toncoin amounts

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're certain a value isn't null:

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

Auto-packing

Structures can be automatically packed to/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 with 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?