Compact versionTolk vs FunC: in short
Comment syntax
| FunC | Tolk |
|---|---|
;; comment | // comment |
{- multiline comment -} | /* multiline comment */ |
Identifiers
- An identifier starts with and continue with .
-
Characters such as
?or:are invalid, so names likefound?orop::increaseare not allowed. -
Identifiers such as
cellorsliceare valid.
number is a valid identifier in TypeScript.
FunC vs Tolk
In FunC, almost any character can be part of an identifier.
For example, 2+2 without spaces is treated as a single identifier, and a variable can be declared with such a name.
In Tolk, spaces are not required. 2+2 is 4, not an identifier. Identifiers can only contain alphanumeric characters.
2+2 evaluates to 4, and 3+~x is interpreted as 3 + (~x), and so on.
| FunC | Tolk |
|---|---|
return 2+2; ;; undefined function 2+2 | return 2+2; // 4 |
| FunC | Tolk |
|---|---|
const op::increase = 0x1234; | const OP_INCREASE = 0x1234 |
int 2+2 = 5; ;; even 2%&!2 is valid | var '2+2' = 5; // don't do like this |
Impure by default, no function call elimination
FunC has animpure function specifier. When absent, a function is treated as pure. If its result is unused, its call is deleted by the compiler.
For example, functions that do not return a value, such as those that throw an exception on a mismatch, are removed. This issue is spoiled by FunC not validating the function body, allowing impure operations to be executed within pure functions.
In Tolk, all functions are impure by default. A function can be marked as pure using an annotation. In pure functions, impure operations such as throwing exceptions, modifying globals, or calling non-pure functions are disallowed.
Function syntax updates
funkeyword
| FunC | Tolk |
|---|---|
cell parse_data(slice cs) { } | fun parse_data(cs: slice): cell { } |
(cell, int) load_storage() { } | fun load_storage(): (cell, int) { } |
() main() { ... } | fun main() { ... } |
- Types of variables — on the right:
| FunC | Tolk |
|---|---|
slice cs = ...; | var cs: slice = ...; |
(cell c, int n) = parse_data(cs); | var (c: cell, n: int) = parse_data(cs); |
global int stake_at; | global stake_at: int |
- Modifiers such as
inline— with@annotations:
| FunC | Tolk |
|---|---|
int f(cell s) inline { | @inline fun f(s: cell): int { |
() load_data() impure inline_ref { | @inline_ref fun load_data() { |
global int stake_at; | global stake_at: int |
forall— this way:
| FunC | Tolk |
|---|---|
forall X -> tuple cons(X head, tuple tail) | fun cons<X>(head: X, tail: tuple): tuple |
asmimplementation — same as in FunC, but properly aligned:
- There is also a
@deprecatedattribute, not affecting compilation but for developers and IDE.
get instead of method_id
In FunC,method_id without arguments declares a get method.
In Tolk, a direct get syntax is used:
| FunC | Tolk |
|---|---|
int seqno() method_id { ... } | get fun seqno(): int { ... } |
method_id(xxx) — uncommon in practice but valid — Tolk uses an annotation:
| FunC | Tolk |
|---|---|
() after_code_upgrade(cont old_code) impure method_id(1666) | @method_id(1666) fun afterCodeUpgrade(oldCode: continuation) |
Parameter types are required, local types are optional
Variables cannot be redeclared in the same scope
loadUint().
In FunC, such methods returned a modified object, so a pattern like
var (cs, int value) = cs.load_int(32) is common.
In Tolk, such methods mutate the object: var value = cs.loadInt(32), so redeclaration is rarely needed:
String postfixes removed, compile-time functions added
Tolk removes FunC-style string postfixes like"..."c and replaces them with compile-time functions.
| FunC | Tolk |
|---|---|
"..."c | stringCrc32("...") |
— | stringCrc16("...") |
"..."H | stringSha256("...") |
"..."h | stringSha256_32("...") |
"..."a | address("...") |
"..."s | stringHexToSlice("...") |
"..."u | stringToBase256("...") |
- compile-time only
- for constant strings only
- usable in constant initialization
Trailing comma support
Tolk supports trailing commas in the following contexts:- tensors
- tuples
- function calls
- function parameters
(5) is not a tensor. It’s the integer 5 in parentheses.
With a trailing comma (5,) it’s still (5).
Optional semicolon for the last statement in a block
In Tolk, the semicolon after the final statement in a block can be omitted. While semicolons are still required between statements, the trailing semicolon on the last statement is now optional.ton(”…”) function for readable Toncoin amounts
| FunC | Tolk |
|---|---|
int cost = 50000000; | val cost = ton("0.05"); |
const ONE_TON = 1000000000; | const ONE_TON = ton("1") |
ton() only accepts constant values. For example, ton(some_var) is invalid.
Its type is coins, not int, although it’s treated as a regular int by the TVM.
Arithmetic operations on coins degrade to int — for example, cost << 1 or cost + ton("0.02") are both valid.
Type system changes
In Tolk v0.7, the type system was rewritten from scratch. To introduce booleans, fixed-width integers, nullability, structures, and generics, Tolk required a static type system similar to TypeScript or Rust. The types are:int,bool,cell,slice,builder, untypedtuple- typed tuple
[T1, T2, ...] - tensor
(T1, T2, ...) - callables
(TArgs) -> TResult - nullable types
T?, compile-time null safety - union types
T1 | T2 | ..., handled with pattern matching coinsand functionton("0.05")int32,uint64, and other fixed-width integers — int at TVM — detailsbytesNandbitsN— similar tointN— backed by slices at TVMaddress— internal/external/none, a slice at TVMvoid— more canonical to be namedunit, butvoidis more reliableself, to make chainable methods, described below; it’s not a type, it can only occur instead of return type of a functionnever— an always-throwing function returnsnever, for example; an impossible type is alsonever- structures and generics
- Variable types can be specified manually or are inferred from declarations, and never change after being declared.
- Function parameters must be strictly typed.
- Function return types, if unspecified, are inferred from return statements similar to TypeScript. In the case of recursion, direct or indirect, the return type must be explicitly declared.
- Generic functions are supported.
Clear and readable type mismatch errors
In FunC, type mismatch errors are hard to interpret:bool type, casting boolVar as int
At the TVM level,bool is represented as -1 or 0, but in the type system, bool and int are distinct types.
- Comparison operators
== / >= /...returnbool. - Logical operators
&& ||returnbool. - Constants
trueandfalsehave thebooltype. Many standard library functions now returnbool, notint:
- Operator
!xsupports bothintandbool. ifconditions and similar statements accept bothintvalues that are not equal to zero andbool.- Logical operators
&&and||accept bothboolandint, preserving compatibility with constructs likea && bwhereaandbare nonzero integers. - Arithmetic operators are restricted to integers. Only bitwise and logical operations are allowed for
bool.
&& and ||, which are absent in FunC, use the if/else asm representation.
In the future, for optimization, they could be automatically replaced with & or | when safe to do so, for example, a > 0 && a < 10.
To manually optimize gas consumption, & and | can be used for bool, but they are not short-circuited.
boolcan be cast tointusingasoperator:
bool is guaranteed to be -1 or 0 at the TVM level, so this is a type-only cast.
Such casts are rarely necessary, except for tricky bitwise optimizations.
Generic functions and instantiations like f<int>(…)
Tolk introduces properly made generic functions. The syntax reminds mainstream languages:T may represent any type, including complex ones:
T is usually inferred from the arguments, there are edge cases where T cannot be inferred because it does not depend on them.
T must be specified externally:
- For asm functions,
Tmust occupy exactly one stack slot. - For user-defined functions,
Tmay represent any structure. - Otherwise, the
asmbody cannot handle it properly.
#include → import
| FunC | Tolk |
|---|---|
#include "another.fc"; | import "another" |
#include "stdlib.fc" is unnecessary. See embedded stdlib.
There is a global naming scope. If the same symbol is declared in multiple files, it results in an error.
import brings all file-level symbols into scope. The export keyword is reserved for future use.
#pragma → compiler options
In FunC, experimental features such asallow-post-modifications were enabled with #pragma directives inside .fc files, which caused inconsistencies across files. These flags are compiler options, not file-level pragmas.
In Tolk, all pragmas were removed. allow-post-modification and compute-asm-ltr are merged into Tolk sources and behave as if they were always enabled in FunC. Instead of pragmas, experimental behavior is set through compiler options.
There is one experimental option: remove-unused-functions, which excludes unused symbols from the Fift output.
#pragma version xxx is replaced with tolk xxx, no >=, only strict versioning. If the version does not match, Tolk shows a warning.
Late symbols resolution and AST representation
In FunC, as in C, a function cannot be accessed before its declaration:null keyword
Creating null values and checking variables for null is now straightforward.| FunC | Tolk |
|---|---|
a = null() | a = null |
if (null?(a)) | if (a == null) |
if (~ null?(b)) | if (b != null) |
if (~ cell_null?(c)) | if (c != null) |
throw and assert keywords
Tolk simplifies exception handling. While FunC providesthrow(), throw_if(), throw_arg_if(), and the corresponding unless forms, Tolk offers two primitives: throw and assert:
| FunC | Tolk |
|---|---|
throw(excNo) | throw excNo |
throw_arg(arg, excNo) | throw (excNo, arg) |
throw_unless(excNo, condition) | assert(condition, excNo) |
throw_if(excNo, condition) | assert(!condition, excNo) |
!condition is valid, as logical NOT is supported.
A verbose form assert(condition, excNo) is also available:
catch arguments: catch (excNo, arg), both of which are optional since arg is usually empty.
| FunC | Tolk |
|---|---|
try { } catch (_, _) { } | try { } catch { } |
try { } catch (_, excNo) { } | try { } catch(excNo) { } |
try { } catch (arg, excNo) { } | try { } catch(excNo, arg) { } |
do … until → do … while
| FunC | Tolk |
|---|---|
do { ... } until (~ condition); | do { ... } while (condition); |
do { ... } until (condition); | do { ... } while (!condition); |
!condition is valid, as logical NOT is supported.
Operator precedence aligned with C++ and JavaScript
In FunC, the codeif (slices_equal() & status == 1) is parsed as if ((slices_equal() & status) == 1). This causes errors in real-world contracts.
In Tolk, & has a lower priority, identical to C++ and JavaScript.
Tolk generates errors on potentially incorrect operator usage to prevent such mistakes:
a << 8 + 1 is equivalent to a << 9, which may be unexpected.
~% ^% /% ~/= ^/= ~%= ^%= ~>>= ^>>= are no longer supported.
Immutable variables declared with val
Like in Kotlin,var declares mutable variables and val declares immutable variables, optionally specifying a type. FunC has no equivalent of val.
mutate parameters. It’s a generalization of FunC ~ tilde functions.
Deprecated command-line options removed
Command-line flags such as-A and -P are removed. The default usage:
- Use
-vto print the version and exit. - Use
-hto list all available flags.
stdlib functions renamed to clear names, camelCase style
All standard library functions now use longer, descriptive names in camelCase style.| FunC | Tolk |
|---|---|
cur_lt() | blockchain.logicalTime() |
car(l) | listGetHead(l) |
get_balance().pair_first() | contract.getOriginalBalance() |
raw_reserve(count) | reserveToncoinsOnBalance(count) |
dict~idict_add?(...) | dict.addIfNotExists(...) |
t~tpush(triple(x, y, z)) | t.push([x, y, z]) |
s.slice_bits() | s.remainingBitsCount() |
~dump(x) | debug.print(x) |
stdlib.fc was split into multiple files, including common.tolk and tvm-dicts.tolk.
See the full comparison: Tolk vs FunC: standard library.
stdlib is now embedded, not downloaded from GitHub
| FunC | Tolk |
|---|---|
| 1. Download stdlib.fc from GitHub | 1. Use standard functions |
| 2. Save into the project | – |
| 3. `#include “stdlib.fc”;“ | – |
| 4.Use standard functions | – |
tolk-js.
The standard library is split into multiple files:
common.tolkfor most common functions,gas-payments.tolkfor gas calculations,tvm-dicts.tolk, and others.
common.tolk are available and implicitly imported by the compiler. Other files must be explicitly imported.
@stdlib/... files as well, with the only exception of common.tolk.
IDE plugins automatically detect the stdlib folder and insert required imports while typing.
Logical operators && ||, logical not !
In FunC, only bitwise operators~ & | ^ exist. Using them as logical operators leads to errors because their behavior is different:
a & b | a && b | note |
|---|---|---|
0 & X = 0 | 0 & X = 0 | sometimes identical |
-1 & X = -1 | -1 & X = -1 | sometimes identical |
1 & 2 = 0 | 1 && 2 = -1 (true) | generally not |
~ found | !found | note |
|---|---|---|
true (-1) → false (0) | -1 → 0 | sometimes identical |
false (0) → true (-1) | 0 → -1 | sometimes identical |
1 → -2 | 1 → 0 (false) | generally not |
condition & f() | condition && f() |
|---|---|
f() is called always | f() is called only if condition |
condition | f() | condition || f() |
f() is called always | f() is called only if condition is false |
&& and || may produce suboptimal Fift code, but the effect is negligible. Use them as in other languages.
| FunC | Tolk |
|---|---|
if (~ found?) | if (!found) |
if (~ found?) {if (cs~load_int(32) == 0) {...} | if (!found && cs.loadInt(32) == 0) {...} |
ifnot (cell_null?(signatures)) | if (signatures != null) |
elseifnot (eq_checksum) | else if (!eqChecksum) |
ifnot and elseifnot are removed because logical NOT is now available. For optimization, Tolk compiler generates IFNOTJMP. The elseif keyword is replaced by the standard else if.
A boolean true transformed as int is -1, not 1. This reflects TVM representation.
Indexed access tensorVar.0 and tupleVar.0
UsetensorVar.{i} to access i-th component of a tensor. Modifying it changes the tensor.
tupleVar.{i} to access the i-th element of a tuple, uses INDEX internally. Modifying it changes the tuple, SETINDEX internally.
- Supports nesting
var.{i}.{j} - Supports nested tensors, nested tuples, and tuples inside tensors
- Supports
mutateand global variables
Type address
In TVM, all binary data is represented as a slice. The same applies to addresses: TL-B defines theMsgAddress type, and the TVM assembler provides instructions to load and validate addresses.
However, at the low level, an address is a slice. Thus, in FunC’s standard library, loadAddress returns slice and storeAddress accepts slice.
Tolk introduces a dedicated address type. It remains a TVM slice at runtime — internal, external, or none — but differs from an abstract slice in terms of the type system:
- Integrated with auto-serialization: the compiler knows how to pack and unpack it using
LDMSGADDRandSTSLICE. - Comparable: operators
==and!=supported for addresses.
- Introspectable: provides
address.isNone(),address.isInternal(),address.isExternal(),address.getWorkchain()andaddress.getWorkchainAndHash(), valid for internal addresses.
address() function, which accepts a standard address. In FunC, this was done using the postfix "..."a, which returned a slice.
slice to address and vice versa
A raw slice that represents an address can be cast using the as operator.
This occurs when an address is manually constructed in a builder using its binary representation:
someAddr as slice.
Different types of addresses
There are different types of addresses. The most frequently used is an internal address — the address of a smart contract. But also, there are external and none addresses. In a binary TL-B representation:
10(internal prefix) +0(anycast, always 0) + workchain (8 bits) + hash (256 bits) — that’sEQ...: it’s 267 bits01(external prefix) + len (9 bits) + len bits — external addresses00(none prefix) — address none, 2 bits
address type can represent any address, although it is most commonly internal.
The type system does not enforce this distinction, as it would require heavy runtime checks.
When receiving an address from untrusted input, validate it:
Type aliases type NewName = <existing type>
Tolk supports type aliases, like in TypeScript and Rust. An alias creates a new name for an existing type and remains fully interchangeable with it.Nullable types T?, null safety, smart casts, operator !
Tolk supports nullable types:int?, cell?, and T? in general, including tensors.
Non-nullable types, such as int and cell, cannot hold null values.
The compiler enforces null safety: nullable types cannot be accessed without a null check.
Checks are applied through smart casts. Smart casts exist only at compile time and do not affect gas or stack usage.
! operator in Tolk provides a compile-time non-null assertion, similar to ! in TypeScript and !!in Kotlin.
It bypasses the compiler’s check for variables that are guaranteed to be non-null.
never:
never type occurs implicitly when a condition is impossible to satisfy:
never in compilation errors usually indicates a warning in the preceding code.
Non-atomic nullable types are supported, e.g., (int, int)?, (int?, int?)?, or ()?.
A special value presence stack slot is added automatically.
It stores 0 for null values and -1 for non-null values.
Union types T1 | T2 | …, operators match, is, !is
Union types allow a variable to hold multiple types, similar to TypeScript.T? are equivalent to T | null.
Union types support intersection properties. For example, B | C can be passed and assigned to A | B | C | D.
The only way to handle union types in code is through pattern matching:
match must cover all union cases and can be used as an expression.
- Commas are optional inside
{}but required in expressions. - A trailing comma is allowed.
- No semicolon is required after
matchwhen used as a statement. - For match-expressions, an arm that terminates has the type
never.
match is allowed:
How are union types represented on the stack, at the TVM level?
How are union types represented on the stack, at the TVM level?
At the TVM level, union types are stored as tagged unions, similar to enums in Rust:
- Each type is assigned a unique type ID, stored alongside the value.
- The union occupies N + 1 stack slots, where N is the maximum size of any type in the union.
- A nullable type
T?is a union withnull(type ID = 0). Atomic types likeint?use a single stack slot.
is. Smart casts behave as follows:
Pattern matching for expressions (switch-like behavior)
match can be used with constant expressions, similar to switch:
- Only constant expressions are allowed on the left-hand side, e.g.,
1,SOME_CONST,2 + 3. - Branches may include
returnorthrow. elseis required for expression form and optional for statement form.
Structures
Similar to TypeScript, but executed at the TVM level.- A struct is a named tensor.
Pointis equivalent to(int, int)at the TVM level.- Field access
p.xcorresponds to tensor element accesst.0for reading and writing.
; or ,,. Both of which are valid, similar to TypeScript.
When creating an object, either StructName { ... } or { ... } can be used if the type is clear from context, such as return type or assignment:
private— accessible only within methods.readonly— immutable after object creation.
Generic structs and aliases
Generic structs and type aliases exist only at the type level and incur no runtime cost.Response<TResult, TError>:
Methods: for any types, including structures
Methods are declared as extension functions, similar to Kotlin. A method that accepts the firstself parameter acts as an instance method; without self, it is a static method.
asm, as self is treated like a regular variable:
self is immutable, preventing modification or calls to mutating methods.
To make self mutable, declare mutate self explicitly:
<T>. The compiler interprets unknown symbols in the receiver type as generic arguments during the parsing process.
T, can be used to define a method that accepts any type:
someObj.method(), the compiler selects the most specific one:
Enums
- Similar to TypeScript and C++ enums
- Distinct type, not
int - Checked during deserialization
- Exhaustive in
match
, , ;, or a newline, similar to struct fields.
Values can be specified manually; unspecified members are auto-calculated.
Color.Red is Color, not int, although it holds the value 0 at runtime.
- Used as variable and parameters
- Extended with methods an enum
- Used in struct fields, unions, generics, and other type contexts
Color is represented as int. Casting between the enum and int is allowed:
Color.Blue as intevaluates to22 as Colorevaluates toColor.Blue
as can produce invalid enum values. This is undefined behavior: for example, 100 as Color is syntactically valid, but program behavior is unpredictable after this point
During deserialization using fromCell(), the compiler performs checks to ensure that encoded integers correspond to valid enum values.
Enums in Tolk differ from Rust. In Rust, each enum member can have a distinct structure. In Tolk, union types provide that capability, so enums are integer constants.
match for enums is exhaustive
Pattern matching on enums requires coverage of all cases:
else can be used to handle remaining values:
== operator can be used to compare integers and addresses:
someColor is Color.Red is invalid syntax.
The is operator is used for type checks.
Given var union: Color | A, u is Color is valid.
Use == to compare enum values.
Enums are allowed in throw and assert
intN or uintN, where N is:
- Specified manually, e.g.,
enum Role: int8 { ... } - Calculated automatically as the minimal N to fit all values
Role above, uint2 is sufficient to fit values 0, 1, 2:
enum Role: int8 with values 0, 1, 2, any input<0 or input>2 triggers exception 5, integer out of range.
This check applies to both value ranges and manually specified enum values:
Auto-detect and inline functions
Tolk can inline functions at the compiler level without usingPROCINLINE as defined by Fift.
@inlineattribute forces inlining.@noinlineprevents a function from being inlined.@inline_refpreserves an inline reference, suitable for rarely executed paths.
- Efficient for stack manipulation.
- Supports arguments of any stack width.
- Works with any functions or methods, except:
- Recursive functions
- Functions containing
returnstatements in the middle
- Supports
mutateandself.
fun Point.getX(self) { return self.x }, do not require stack reordering.
Small functions can be extracted without runtime cost.
The compiler handles inlining; no inlining is deferred to Fift.
How does auto-inline work?
- Simple, small functions are always inlined
- Functions called only once are always inlined
- If
weight < THRESHOLD, the function is always inlined - If
usages == 1, the function is always inlined - Otherwise, an empirical formula determines inlining
@inline annotation can be applied to large functions when all usages correspond to hot paths.
Inlining can also be disabled with @inline_ref, even for functions called once. For example, in unlikely execution paths.
For optimization, use gas benchmarks and experiment with inlining and branch reordering.
What can NOT be auto-inlined?
A function is NOT inlined, even if marked with @inline, in the following cases:
- The function contains
returnin the middle. Multiple return points are unsupported for inlining. - The function participates in a recursive call chain
f -> g -> f. - The function is used as a non-call. For example, when a reference is taken:
val callback = f.
No tilde ~ methods, mutate keyword instead
In FunC, both.methods() and ~methods() exist.
In Tolk, only the dot syntax is used, and methods are called as .method().
Tolk follows expected behavior:
Auto-packing to/from cells/builders/slices
Any struct can be automatically packed into a cell or unpacked from one:Universal createMessage: avoid manual cells composition
No need for manualbeginCell().storeUint(...).storeRef(...) boilerplate — describe the message in a literal and the compiler handles packing.
map<K,V> instead of low-level TVM dictionaries
Tolk introducesmap<K, V>:
- A generic type
map<K, V>— any serializable keys and values. - The compiler automatically generates
asminstructions and performs (de)serialization on demand. - Natural syntax for iterating forwards, backwards, or starting from a specified key.
- Zero overhead compared to low-level approach.
set, exists, get, etc.
m.get(key) returns not an “optional value”, but isFound + loadValue()
m.get(key)returns a struct, NOTV?.m.mustGet(key)returnsVand throws if the key is missing.
- Gas consumption; zero overhead.
- Nullable values can be supported, such as
map<int32, address?>ormap<K, Point?>. - Returning
V?, makes it impossible to distinguish between “key exists but value is null” and “key does not exist”.
foreach. Iteration follows this pattern:
- define the starting key:
r = m.findFirst()orr = m.findLast() - while
r.isFound:- use
r.getKey()andr.loadValue() - move the cursor:
r = m.iterateNext(r)orr = m.iteratePrev(r)
- use
while (r.isFound), not while (r == null).
As with m.get(key), existence is checked through isFound.
m.isEmpty(), not m == null. Since map is a dedicated type, it must be checked with isEmpty(), because m == null does not work.
Suppose a wrapper over dictionaries is implemented:
var m: MyMap, calling m.isEmpty() works. The expression m == null is invalid. The compiler issues the following warning:
var m: map<...>?. This variable can be null and not null. When not null, it can contain an empty map or a non-empty map. The expression m == null only makes sense for nullable maps.
Allowed types for K and V
All the following key and value types are valid:
- Keys must be fixed-width and contain zero references
- Valid: int32, uint64, address, bits256, Point
- Invalid: int, coins, cell
- Values must be serializable
- Valid: int32, coins, AnyStruct, Cell<AnyStruct>
- Invalid: int, builder
intN, uintN, or address. Values can be any serializable type.
At the TVM level, keys can be numbers or slices. Complex keys, such as Point, are automatically serialized and deserialized by the compiler.
Available methods for maps
JetBrains IDE and VS Code provide method suggestions. Most methods are self-explanatory.createEmptyMap<K, V>(): map<K, V>
PUSHNULL since TVM NULL represents an empty map.
createMapFromLowLevelDict<K, V>(d: dict): map<K, V>
map.get or similar methods.
m.toLowLevelDict(): dict
m.isEmpty(): bool
m.isEmpty() instead of m == null.
m.exists(key: K): bool
m.get(key: K): MapLookupResult<V>
isFound = false if key does not exist.
m.mustGet(key: K, throwIfNotFound: int = 9): V
m.set(key: K, value: V): self
self, calls may be chained.
m.setAndGetPrevious(key: K, value: V): MapLookupResult<V>
isFound = false.
m.replaceIfExists(key: K, value: V): bool
m.replaceAndGetPrevious(key: K, value: V): MapLookupResult<V>
m.addIfNotExists(key: K, value: V): bool
m.addOrGetExisting(key: K, value: V): MapLookupResult<V>
m.delete(key: K): bool
m.deleteAndGetDeleted(key: K): MapLookupResult<V>
isFound = false.
m.findFirst(): MapEntry<K, V>
isFound = false for an empty map.
m.findLast(): MapEntry<K, V>
isFound = false for an empty map.
m.findKeyGreater(pivotKey: K): MapEntry<K, V>
pivotKey.
m.findKeyGreaterOrEqual(pivotKey: K): MapEntry<K, V>
m.findKeyLess(pivotKey: K): MapEntry<K, V>
m.findKeyLessOrEqual(pivotKey: K): MapEntry<K, V>
m.iterateNext(current: MapEntry<K, V>): MapEntry<K, V>
m.iteratePrev(current: MapEntry<K, V>): MapEntry<K, V>
- Prefix dictionaries:
import @stdlib/tvm-dictsand use assembly functions. - Augmented hashmaps and Merkle proofs: implement interaction manually.
Modern onInternalMessage
In Tolk,msg_cell does not require manual parsing to retrieve sender_address or fwd_fee. Fields are accessed directly:
recv_internal, works but is less efficient. InMessage fields are directly mapped to TVM-11 instructions.
Recommended pattern:
- Define each message as a struct, typically including a 32-bit opcode.
- Define a union of all allowed messages.
- Use
val msg = lazy MyUnion.fromSlice(in.body). - Match on
msg, handling each branch and possibly anelse.
fwd_fee or other fields at the start of the function. Access them on demand through the in.smth.
onBouncedMessage
In FunC, msg_cell required parsing, reading 4-bit flags, and testing flags & 1 to detect a bounced message.
In Tolk, bounced messages are handled through a separate entry point:
onBouncedMessage is not declared, bounced messages are filtered out:
Next steps
Explore the Tolk vs FunC benchmarks —real Jetton, NFT, and Wallet contracts migrated from FunC with the same logic. Use the FunC-to-Tolk converter for incremental migration. Runnpm create ton@latest to experiment.