Language guide
Table of contents
- Basic syntax
- Functions
- Variables and types
- Control flow
- Type system
- Collections
- Error handling
- Structures
- Methods
- Imports and modules
- Advanced features
- 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 likeint32
anduint64
are also supportedbool
- boolean (true
/false
). Note: in TVM,true
is represented as-1
, not1
cell
- a TVM cellslice
- a TVM slice; you can also usebitsN
. (e.g.bits512
for storing a signature)builder
- a TVM builderaddress
- blockchain addresscoins
- Toncoin or coin amountsvoid
- no return valuenever
- 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.