Documentation Index
Fetch the complete documentation index at: https://companyname-a7d5b98e-3-gasless.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
FunC is the first high-level language for writing smart contracts in TON.
For years, it was the only option.
Lots of production code was written in FunC, and it will always be alive on-chain and in developers’ hearts.
Tolk replaces FunC with modern syntax, a robust type system, and built-in serialization — while generating efficient assembly code.
Released in 2025, now it is considered the primary language for the TON ecosystem.
How to migrate from FunC to Tolk
- Scan the list below to get the overall picture.
- Explore the tolk-bench repo as a source of reference contracts.
- Use the FunC-to-Tolk converter to migrate existing projects.
Gas benchmarks
The tolk-bench repository compares FunC and Tolk on several TEPs.
For every metric measured, gas consumption reduced 30–50%. Primarily it’s a result of the language design.
What Tolk and FunC have in common
Both languages target into Fift assembler.
Tolk is not “a wrapper” that transpiles to FunC — it has its own semantic and optimization kernel.
Both languages work on TVM after being compiled to bytecode.
TVM is a stack machine, imposing architectural and runtime restrictions.
Both languages have IDE plugins, although support for Tolk is way better:
JetBrains IDEs, VS Code, Cursor, Windsurf, etc.
Both languages are available in blueprint and other client-side tooling.
Command-line mode is also supported.
But all language aspects are completely different — a huge list below.
List of “Tolk vs FunC” differences
Tolk and FunC are completely different.
It’s even inaccurate to compare them — the difference lies in the design, not in syntax.
Nevertheless, let’s try to summarize the details.
Tolk reminds TypeScript and Rust
- FunC: resembles C (“FunC” stands for “functional C”)
- Tolk: resembles TypeScript, Rust, and Kotlin
fun sum(a: int, b: int): int {
return a + b;
}
See: basic syntax.
Tolk has structures
- FunC: return long unnamed tensors such as
(int, slice, int, int)
- Tolk: declare a struct, it’s the same efficient
struct Demo {
previousValue: int256
ownerAddress: address
effectsCount: uint32
totalAmount: coins
}
See: structures.
Automatic serialization
- FunC: manual bit-level work with builders and slices
- Tolk: declare a struct and call
fromCell and toCell
struct Point {
x: int8
y: int8
}
fun demo() {
var value: Point = { x: 10, y: 20 };
// makes a cell containing "0A14" (hex)
var c = value.toCell();
// back to { x: 10, y: 20 }
var p = Point.fromCell(c);
}
Pay attention to the use of int8, uint64, coins — all of them are TVM integers (see numbers).
See: automatic serialization.
Lazy loading
- FunC: for optimization, manual juggling with preloads and skips
- Tolk: the
lazy keyword loads only requested fields skipping the rest
get fun publicKey() {
val st = lazy Storage.load();
// <-- here "skip 65 bits, preload uint256" is inserted
return st.publicKey
}
See: lazy loading.
The bool type
- FunC: only integers, ‘true’ is
-1, ‘false’ is 0; ifnot
- Tolk: type
bool and logical operators && || ! are supported
if (trustInput || validate(input)) {
// ...
}
See: booleans.
The address type
- FunC: only slices (binary data); parse and compare bits
- Tolk: type
address with convenient methods and operator ==
if (in.senderAddress == storage.ownerAddress) {
val workchain = storage.ownerAddress.getWorkchain();
// ...
}
See: address.
Null safety
- FunC: any variable can hold
null, which may lead to runtime errors
- Tolk: provides nullable types
T?, null safety, and smart casts
fun checkWithOptional(a: int, b: int?): bool {
if (b == null) {
return checkSingle(a);
}
return b >= 0 && checkDouble(a, b);
}
See: nullability.
Everything else in the type system
- FunC: several types exposing TVM primitives
- Tolk: a wide range of types, including unions, generics, and enums
struct Container<T> {
element: T?
}
struct Nothing
type Wrapper<T> = Nothing | Container<T>
See: type system overview.
Methods for any types
- FunC: global-scope functions only
- Tolk: both functions and methods — for structures and even primitives
// no `self` — static method
fun Point.createZero(): Point {
return { x: 0, y: 0 }
}
// has `self` — instance method
fun Point.sumCoords(self) {
return self.x + self.y
}
// even for primitives: cells, integers, tuples, etc.
fun tuple.isEmpty(self) {
return self.size() == 0
}
See: functions and methods.
No impure keyword
- FunC: once
impure is forgotten, a call may be dropped
- Tolk: the compiler does not remove user function calls
fun validate(input: SomeStruct) {
// ...
}
No ~tilde methods
- FunC:
x~f() and x.f() are different (mutating and not)
- Tolk: only the dot — a single, consistent way to call methods
val delta = someSlice.loadUint(32); // mutates someSlice
val owner = someSlice.loadAddress();
See: mutability.
Native maps over TVM dictionaries
- FunC:
m~idict_set_builder(1,32,begin_cell().store_uint(10,32))
- Tolk:
m.set(1, 10)
var m: map<int8, int32> = createEmptyMap();
m.set(1, 10);
m.addIfNotExists(2, -20);
m.delete(2); // now: [ 1 => 10 ]
See: maps.
Modern message handling
- FunC:
() recv_internal(4 params) and parse a message cell
- Tolk:
fun onInternalMessage(in) and use in.senderAddress, etc.
fun onInternalMessage(in: InMessage) {
// internal non-bounced messages arrive here
in.senderAddress;
in.originalForwardFee;
// and other fields
}
fun onBouncedMessage(in: InMessageBounced) {
// bounced messages arrive here
}
See: message handling.
No if (op == OP_TRANSFER) for opcodes
- FunC:
if-else to route an incoming message based on opcode
- Tolk: use union types and pattern matching
type MyMessage =
| CounterIncBy
| CounterReset
// ...
fun onInternalMessage(in: InMessage) {
val msg = lazy MyMessage.fromSlice(in.body);
match (msg) {
CounterIncBy => {
// ...
}
CounterReset => {
// ...
}
// ...
}
}
See: pattern matching.
No “ignore empty messages” pattern
- FunC:
recv_internal() starts with if (slice_empty?(...))
- Tolk: just use
else in match
fun onInternalMessage(in: InMessage) {
val msg = lazy MyMessage.fromSlice(in.body);
match (msg) {
CounterReset => { /* ... */ }
// ... handle all variants of the union
else => {
// for example: ignore empty messages
if (in.body.isEmpty()) {
return
}
throw 0xFFFF
}
}
}
See: lazy matching.
Native message composition
- FunC:
store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) etc.
- Tolk:
createMessage that auto-detects body ref or not
val reply = createMessage({
bounce: BounceMode.NoBounce,
value: ton("0.05"),
dest: senderAddress,
body: RequestedInfo { ... }
});
reply.send(SEND_MODE_REGULAR);
See: message sending.
Native deployment and StateInit
- FunC: manually pack contract’s code and data according to TL-B
- Tolk:
createMessage auto-computes destination
val deployMsg = createMessage({
// address auto-calculated, code+data auto-attached
dest: {
stateInit: {
code: contractCodeCell,
data: emptyStorage.toCell(),
},
// optionally control workchains and sharding
}
});
See: message sending.
op::increase is not a valid identifier
- FunC: allows any symbols in identifiers, even
var 2+2 = ... is ok
- Tolk: alphanumeric identifiers,
2+2 is 4, as expected
const OP_INCREASE = 0x12345678
See: variables.
Small functions are inlined automatically
- FunC: prefer larger functions for reduced gas consumption
- Tolk: the compiler auto-inlines functions with zero overhead
fun int.zero() {
return 0
}
fun int.inc(mutate self, byValue: int = 1): self {
self += byValue;
return self;
}
fun main() {
return int.zero().inc().inc()
}
is reduced to “return 2” in assembler:
main() PROC:<{
2 PUSHINT
}>
Note: inline modifier in FunC works at the Fift level, it’s sub-optimal due to extra stack permutations.
In Tolk, inlining works at the compiler level and is combined with constant folding.
See: compiler optimizations.
Consecutive builder.storeUint are merged
- FunC: manually combine constant stores into
b.storeUint(0x18,6)
- Tolk: merges
b.storeUint(...).storeUint(...) if constant
b.storeUint(0, 1)
.storeUint(1, 1)
.storeUint(1, 1)
.storeUint(0, 1)
.storeUint(0, 2)
is translated to just
See: compiler optimizations.
Standard library redesigned
Functions from stdlib.fc now use longer, descriptive naming:
| FunC | Tolk |
|---|
cur_lt() | blockchain.logicalTime() |
car(l) | listGetHead(l) |
raw_reserve(coins) | reserveToncoinsOnBalance(coins) |
~dump(x) | debug.print(x) |
Many global-scope functions became methods for primitives:
| FunC | Tolk |
|---|
s.slice_hash() | s.hash() |
equal_slices_bits(a, b) | a.bitsEqual(b) |
t.tuple_len() | t.size() |
t~tpush(triple(x, y, z)) | t.push([x, y, z]) |
String postfixes like "..."c became built-in functions:
| FunC | Tolk |
|---|
"..."c | stringCrc32("...") |
"..."H | stringSha256("...") |
"..."h | stringSha256_32("...") |
"..."a | address("...") |
"..."s | stringHexToSlice("...") |
"..."u | stringToBase256("...") |
See: differences in a standard library.
… and of course — assembler functions
Regardless of being a high-level language, Tolk provides all low-level capabilities.
The code can still be written in a “FunC-style” with manual builders and slices,
exotic TVM instructions can still be used.
@pure
fun incThenNegate(v: int): int
asm "INC" "NEGATE"
See: assembler functions.