Skip to main content
Tolk supports map<K, V> — a high‑level type that encapsulates TVM dictionaries:
  • Any serializable keys and values.
  • Natural syntax for iterating forwards, backwards, or starting from a specified key.
  • Zero overhead compared to low-level approach.

Create an empty map

A map is a dedicated type, it may be used in parameters, fields, etc.:

Add values to a map

Use m.set(k, v) and other methods suggested by an IDE after a dot (a full list is available below):

Get a value by key

m.get(key) returns isFound + loadValue():
Note: check not r == null, but r.isFound. In other words, map.get(key) returns not V?, but a special result. Also use m.mustGet(key) that returns V and throws if the key is missing:

Iterate forward and backward

There is no dedicated foreach syntax. Iteration follows this pattern:
  • define the starting key: r = m.findFirst() or r = m.findLast()
  • while r.isFound:
    • use r.getKey() and r.loadValue()
    • move the cursor: r = m.iterateNext(r) or r = m.iteratePrev(r)
Example: iterate all keys forward
Example: iterate backwards from keys ≤ 2

Check if a map is empty

Allowed types for K and V

All the following key and value types are valid:
Some types are not allowed. General rules:
  • Keys must be fixed-width and contain zero references
    • Valid: int32, address, bits256, Point
    • Invalid: int, coins, cell
  • Values must be serializable
    • Valid: coins, AnyStruct, Cell<AnyStruct>
    • Invalid: int, builder
In practice, keys are most commonly intN, uintN, or address. Values can be any serializable type.

Available methods for maps

An IDE suggests available methods after a dot. Most methods are self-explanatory.
  • createEmptyMap<K, V>(): map<K, V>
Returns an empty typed map. Equivalent to PUSHNULL since TVM NULL represents an empty map.
  • createMapFromLowLevelDict<K, V>(d: dict): map<K, V>
Converts a low-level TVM dictionary to a typed map. Accepts an optional cell and returns the same optional cell. Mismatched key or value types result in failures when calling map.get or related methods.
  • m.toLowLevelDict(): dict
Converts a high-level map to a low-level TVM dictionary. Returns the same optional cell.
  • m.isEmpty(): bool
Checks whether a map is empty. Use m.isEmpty() instead of m == null.
  • m.exists(key: K): bool
Checks whether a key exists in a map.
  • m.get(key: K): MapLookupResult<V>
Gets an element by key. Returns isFound = false if key does not exist.
  • m.mustGet(key: K, throwIfNotFound: int = 9): V
Gets an element by key and throws if it does not exist.
  • m.set(key: K, value: V): self
Sets an element by key. Since it returns self, calls may be chained.
  • m.setAndGetPrevious(key: K, value: V): MapLookupResult<V>
Sets an element and returns the previous element. If no previous element, isFound = false.
  • m.replaceIfExists(key: K, value: V): bool
Sets an element only if the key exists. Returns whether an element was replaced.
  • m.replaceAndGetPrevious(key: K, value: V): MapLookupResult<V>
Sets an element only if the key exists and returns the previous element.
  • m.addIfNotExists(key: K, value: V): bool
Sets an element only if the key does not exist. Returns true if added.
  • m.addOrGetExisting(key: K, value: V): MapLookupResult<V>
Sets an element only if the key does not exist. If exists, returns an old value.
  • m.delete(key: K): bool
Deletes an element by key. Returns true if deleted.
  • m.deleteAndGetDeleted(key: K): MapLookupResult<V>
Deletes an element by key and returns the deleted element. If not found, isFound = false.
  • m.findFirst(): MapEntry<K, V>
  • m.findLast(): MapEntry<K, V>
Finds the first (minimal) or last (maximal) element. For integer keys, returns minimal (maximal) integer. For addresses or complex keys (represented as slices), returns lexicographically smallest (largest) key. Returns isFound = false when the map is empty.
  • m.findKeyGreater(pivotKey: K): MapEntry<K, V>
  • m.findKeyGreaterOrEqual(pivotKey: K): MapEntry<K, V>
  • m.findKeyLess(pivotKey: K): MapEntry<K, V>
  • m.findKeyLessOrEqual(pivotKey: K): MapEntry<K, V>
Finds an element with key compared to pivotKey.
  • m.iterateNext(current: MapEntry<K, V>): MapEntry<K, V>
  • m.iteratePrev(current: MapEntry<K, V>): MapEntry<K, V>
Iterates over a map in ascending (descending) order.

Augmented hashmaps and prefix dictionaries

These structures are rarely used and are not part of the Tolk type system.
  • Prefix dictionaries: import @stdlib/tvm-dicts and use assembly functions.
  • Augmented hashmaps and Merkle proofs: implement interaction manually.

Keys are auto-serialized

At the TVM level, keys can be numbers or slices. Complex keys, such as Point, are automatically serialized and deserialized by the compiler.
If a key is a struct with a single intN field, it behaves like a number.

How to emulate Set<T> with maps

Use an “empty tensor” as a type for V:
It will work, a bit noisy. Lots of methods for maps are just inapplicable to sets, so its “public interface” is wrong. Sets have a much simpler API, literally 4 functions. It’s better to create a simple wrapper:

Low-level: why “isFound” but not “optional value”?

There are two reasons for this design:
  • Gas consumption (zero overhead)
  • Nullable values can be supported, like map<int32, address?> or map<K, Point?>. Returning V?, makes it impossible to distinguish between “key exists but value is null” and “key does not exist”.
TVM dictionaries store binary data. Having a map<K, int32> and doing m.set(k, 10), this “10” is actually 0x0000000A (automatically packed by the compiler). All TVM instructions for reading return slices, so at some point, those bits should be decoded back to “10”. TVM instructions put two values on a stack: (slice -1) or (null 0). If a choice is to return V?, the compiler needs to do something like
Then, at usage, it’s compared null:
So, it results in two runtime checks and three TVM continuations. That’s why instead of V?, a special struct is returned:
This struct directly maps onto the TVM stack: (slice -1) or (null 0). The condition if (r.isFound) naturally checks the top element (automatically popped). Followed by auto-deserialization at r.loadValue() when rawSlice is left on the top. Moreover, some other functions return the same struct. For example, m.setAndGetPrevious:
Overall, this provides zero overhead compared to plain dictionaries.

Stack layout and serialization

An empty map is backed by TVM NULL, serialized as ‘0’. A non-empty map is TVM CELL, serialized as ‘1’+ref. For details, follow TVM representation and Serialization.