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 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 cellslice
- TVM slice, you can also usebitsN
. (e.g.bits512
for storing signature)builder
- TVM builderaddress
- blockchain addresscoins
- Toncoin/coins amountsvoid
- no return valuenever
- 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.