Skip to main content
No ~ tilde methods in Tolk. For example:
  • cs.loadInt(32) modifies a slice and returns an integer.
  • b.storeInt(x, 32) modifies a builder.
  • b = b.storeInt() also works because the method returns self.
Method chaining works the same as in JavaScript and produces identical Fift instructions, without runtime overhead. Custom methods can also be defined.
In FunC, methods can be called as .method() or ~method(). In Tolk, all methods use a dot: .method(). A method may or may not mutate the object. Tolk defines a mutability model that generalizes the behavior of the ~ tilde in FunC. Behavior and semantics differ from FunC. Tolk method calls are designed to behave similarly to JavaScript:
FunCTolk
int flags = cs~load_uint(32);var flags = cs.loadUint(32);
(cs, int flags) = cs.load_uint(32);var flags = cs.loadUint(32);
(slice cs2, int flags) = cs.load_uint(32);var cs2 = cs; var flags = cs2.loadUint(32);
slice data = get_data().begin_parse(); int flag = data~load_uint(32);val flag = contract.getData().beginParse().loadUint(32);
dict~udict_set(...);dict.set(...);
b~store_uint(x, 32);b.storeInt(x, 32);
b = b.store_int(x, 32);b.storeInt(x, 32); // also works b = b.storeUint(32);
b = b.store_int(x, 32).store_int(y, 32);b.storeInt(x, 32).storeInt(y, 32); // also works b = ...;

Value semantics

By default, function arguments in Tolk are copied by value. Function calls do not modify the original data.
fun someFn(x: int) {
    x += 1;
}

var origX = 0;
someFn(origX);  // origX remains 0
someFn(10);     // int
origX.someFn(); // still allowed, but not recommended; origX remains 0
This also applies to slices, cells, and other types:
fun readFlags(cs: slice) {
    return cs.loadInt(32);
}

var flags = readFlags(msgBody);  // msgBody is not modified
// msgBody.loadInt(32) reads the same flags

Mutating function parameters

Adding the mutate keyword makes a parameter mutable. To prevent unintended modifications, mutate must also be specified when calling the function.
fun increment(mutate x: int) {
    x += 1;
}

// it's correct, simple and straightforward
var origX = 0;
increment(mutate origX);  // origX becomes 1

// these are compiler errors
increment(origX);         // error, unexpected mutation
increment(10);            // error, not lvalue
origX.increment();        // error, not a method, unexpected mutation
val constX = getSome();
increment(mutate constX); // error, it's immutable since `val`
This also applies to slices and other types:
fun readFlags(mutate cs: slice) {
    return cs.loadInt(32);
}

val flags = readFlags(mutate msgBody);
// msgBody.loadInt(32) will read the next integer
A function can define multiple mutate parameters:
fun incrementXY(mutate x: int, mutate y: int, byValue: int) {
    x += byValue;
    y += byValue;
}

incrementXY(mutate origX, mutate origY, 10);   // both += 10
This behavior is similar to passing by reference, but since ref is already used in TON for cells and slices, the keyword mutate is used instead.

Instance methods and self

Methods — unlike global functions fun f() — are declared as fun receiver_type.f(). If a method accepts self, it is an instance method; otherwise, it is static.
fun int.assertNotEq(self, throwIfEq: int) {
    if (self == throwIfEq) {
        throw 100;
    }
}

someN.assertNotEq(10);
10.assertNotEq(10);      // also ok, since self is not mutating
By default, self is immutable. The method cannot modify the object.
fun slice.readFlags(self) {
    return self.loadInt(32);  // error, modifying immutable variable
}

fun slice.preloadInt32(self) {
    return self.preloadInt(32);  // ok, it's a read-only method
}

Mutating methods with self

Combining mutate with self defines a method that modifies the object and is called using the dot syntax. Example:
fun slice.readFlags(mutate self) {
    return self.loadInt(32);
}

val flags = msgBody.readFlags();

fun int.increment(mutate self) {
    self += 1;
}

var origX = 10;
origX.increment();    // 11
10.increment();       // error, not lvalue

// Method can also mutate multiple arguments:
fun int.incrementWithY(mutate self, mutate y: int, byValue: int) {
    self += byValue;
    y += byValue;
}

origX.incrementWithY(mutate origY, 10);   // both += 10
The standard library includes many mutate mutate self, such as in tuples and dictionaries. In FunC, equivalent mutating methods use the tilde ~.
@pure
fun tuple.push<X>(mutate self, value: X): void
    asm "TPUSH"

t.push(1);

Returning self for chaining

Returning self works as return self in Python or return this in JavaScript. It makes methods such as storeInt() chainable.
fun builder.storeInt32(mutate self, x: int): self {
    self.storeInt(x, 32);
    return self;

    // this also works as expected (the same Fift code)
    // return self.storeInt(x, 32);
}

var b = beginCell().storeInt(1, 32).storeInt32(2).storeInt(3, 32);
b.storeInt32(4);     // // works without assignment because it mutates b directly
b = b.storeInt32(5); // works with assignment, since also returns
The return type must be explicitly declared as self. Omitting it causes a compilation error.

Mutate self in asm functions

The same behavior can also be implemented in asm functions. A mutation in the compiler works as an implicit return and reassignment of mutate parameters. Example:
// returns (int, void)
fun increment(mutate x: int): void { ... }

// does: (x', _) = increment(x); x = x'
increment(mutate x);

// returns (int, int, (slice, cell))
fun f2(mutate x: int, mutate y: int): (slice, cell) { ... }

// does: (x', y', r) = f2(x, y); x = x'; y = y'; someF(r)
someF(f2(mutate x, mutate y));

// when `self`, it's the same
// does: (cs', r) = loadInt(cs, 32); cs = cs'; flags = r
flags = cs.loadInt(32);
Therefore, an asm function should place self' onto the stack before returning the result:
// "TPUSH" pops (tuple) and pushes (tuple')
// so, self' = tuple', and return an empty tensor
// `void` is a synonym for an empty tensor
fun tuple.push<X>(mutate self, value: X): void
    asm "TPUSH"

// "LDU" pops (slice) and pushes (int, slice')
// with asm(-> 1 0), we make it (slice', int)
// so, self' = slice', and return int
fun slice.loadMessageFlags(mutate self): int
    asm(-> 1 0) "4 LDU"
To return self, specify a return type. The compiler handles the rest:
// "STU" pops (int, builder) and pushes (builder')
// with asm(op self), we put arguments to correct order
// so, self' = builder', and return an empty tensor
// but to make it chainable, `self` instead of `void`
fun builder.storeMessageOp(mutate self, op: int): self
    asm(op self) "32 STU"
Low-level constructs are rarely needed. Wrappers around existing functions are usually enough.
// just do it like this, without asm; it's the same effective

fun slice.myLoadMessageFlags(mutate self): int {
    return self.loadUint(4);
}

fun builder.myStoreMessageOp(mutate self, flags: int): self {
    return self.storeUint(32, flags);
}