Documentation Index
Fetch the complete documentation index at: https://companyname-a7d5b98e-closes-allow-word-breaks-in-tables.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
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:
| FunC | Tolk |
|---|
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);
}