Skip to main content
FunC is the first high-level language for TON smart contracts. Legacy FunC codebases exist, but the compiler is no longer maintained. Tolk is the primary and only actively supported language in the TON ecosystem.

Migrating from FunC to Tolk

  1. Review the list of differences.
  2. Refer to the reference contracts on GitHub.
  3. Use the FunC-to-Tolk converter to migrate existing projects.

Gas benchmarks

The tolk-bench repository compares FunC and Tolk across several TEPs. Across all measured metrics, Tolk reduces gas consumption by 30–50% compared to FunC. This reduction is primarily due to differences in language design.

Common characteristics

  • Both languages compile to Fift assembler.
  • Both languages run on TVM after compilation to bitcode.
  • Both languages are supported by IDE plugins.
  • Both languages are available in Blueprint and other client-side tooling.
  • Both support command-line usage.

Key differences

The differences between Tolk and FunC are primarily in language design rather than syntax.

Basic syntax

  • FunC resembles C, the name stands for “functional C”.
  • Tolk resembles TypeScript, Rust, and Kotlin.
fun sum(a: int, b: int): int {
    return a + b;
}

Structures

  • FunC uses unnamed tensors such as (int, slice, int, int).
  • Tolk uses named structures with the same runtime efficiency.
struct Demo {
    previousValue: int256
    ownerAddress: address
    effectsCount: uint32
    totalAmount: coins
}

Automatic serialization

  • FunC requires manual bit-level serialization using builders and slices.
  • Tolk derives serialization from struct using toCell and fromCell.
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);
}
All integer types such as int8, uint64, and coins are TVM integers.

Lazy loading

  • FunC requires manual control over preloads and skips for optimization.
  • Tolk uses the lazy keyword to load only accessed fields.
get fun publicKey() {
    val st = lazy Storage.load();
    // <-- here "skip 65 bits, preload uint256" is inserted
    return st.publicKey
}

Boolean type

  • FunC represents only integers: -1 for true, 0 for false; ifnot.
  • Tolk provides a bool type and logical operators &&, ||, and !.
if (trustInput || validate(input)) {
    // ...
}

Address type

  • FunC represents only slices; bits comparison and parsing.
  • Tolk provides a address type with methods and the == operator.
if (in.senderAddress == storage.ownerAddress) {
    val workchain = storage.ownerAddress.getWorkchain();
    // ...
}

Null safety

  • FunC allows any variable to 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);
}

Type system features

  • FunC provides several types that expose TVM primitives.
  • Tolk provides a type system, including unions, generics, and enums.
struct Container<T> {
    element: T?
}

struct Nothing

type Wrapper<T> = Nothing | Container<T>

Methods for all types

  • FunC provides functions in the global scope only.
  • Tolk provides both functions and methods, applicable to structures and 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
}

No impure keyword

  • In FunC, if impure is omitted, a function call may be dropped.
  • In Tolk, user function calls are not removed by the compiler.
fun validate(input: SomeStruct) {
    // ...
}

No ~ tilde methods

  • FunC distinguishes between x~f() and x.f().
  • Tolk uses a dot . syntax for method calls.
val delta = someSlice.loadUint(32);   // mutates someSlice
val owner = someSlice.loadAddress();

Native maps over TVM dictionaries

  • FunC uses dictionaries, for example m~idict_set_builder(1,32,begin_cell().store_uint(10,32)).
  • Tolk provides native maps, for example m.set(1, 10).
var m: map<int8, int32> = createEmptyMap();
m.set(1, 10);
m.addIfNotExists(2, -20);
m.delete(2);   // now: [ 1 => 10 ]

Message handling

  • FunC defines () recv_internal(4 params) and parses a message cell.
  • Tolk provides 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
}

Message routing

  • FunC routes incoming messages using if-else checks on the opcode, for example if (op == OP_TRANSFER).
  • Tolk routes messages using union types and pattern matching.
type MyMessage =
    | CounterIncBy
    | CounterReset
    // ...

fun onInternalMessage(in: InMessage) {
    val msg = lazy MyMessage.fromSlice(in.body);
    match (msg) {
        CounterIncBy => {
            // ...
        }
        CounterReset => {
            // ...
        }
        // ...
    }
}

Empty messages handling

  • FunC checks for empty message bodies using if (slice_empty?(...)) at the beginning of recv_internal().
  • Tolk handles empty or unknown messages using else in a lazy matching.
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
        }
    }
}

Message composition

  • FunC requires manual bit-level message construction, for example store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1).
  • Tolk provides createMessage, which automatically chooses between inline body and body reference.
val reply = createMessage({
    bounce: BounceMode.NoBounce,
    value: ton("0.05"),
    dest: senderAddress,
    body: RequestedInfo { ... }
});
reply.send(SEND_MODE_REGULAR);

Deployment and StateInit

  • FunC requires manual packing of contract code and data according to TL-B.
  • Tolk uses createMessage to attach StateInit and compute the destination automatically.
val deployMsg = createMessage({
    // address auto-calculated, code+data auto-attached
    dest: {
        stateInit: {
            code: contractCodeCell,
            data: emptyStorage.toCell(),
        },
        // optionally control workchains and sharding
    }
});

Identifier syntax

  • FunC allows arbitrary symbols in identifiers, for example var 2+2 = ....
  • Tolk allows only alphanumeric identifiers, for example 2+2 is 4.
const OP_INCREASE = 0x12345678

Automatic inlining of small functions

  • In FunC, prefer larger functions for reduced gas consumption.
  • In Tolk, the compiler auto-inlines functions without additional gas cost.
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
}>
In FunC, inline modifier operates at the Fift level and may introduce extra stack permutations. In Tolk, inlining is performed at the compiler level and is combined with constant folding.

Merging consecutive builder.storeUint

  • FunC manually combines 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)
Translated to:
b{011000} STSLICECONST

Standard library redesigned

There’re differences in a standard library. For example, functions from stdlib.fc use descriptive naming:
FunCTolk
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:
FunCTolk
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:
FunCTolk
"..."cstringCrc32("...")
"..."HstringSha256("...")
"..."hstringSha256_32("...")
"..."aaddress("...")
"..."sstringHexToSlice("...")
"..."ustringToBase256("...")

Assembler functions

Although Tolk is a high-level language, it exposes low-level capabilities. Code may still be written in a FunC-style with manual builders and slices, and TVM instructions are supported.
@pure
fun incThenNegate(v: int): int
    asm "INC" "NEGATE"