Skip to main content
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
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

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);
}