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
Add values to a map
Usem.set(k, v), m.delete(k), and other suggested methods (a full list is available below):
Get a value by key
m.get(key) returns isFound + loadValue():
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 dedicatedforeach syntax. 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
Check if a map is empty
For experienced readers
At the TVM level, an empty map is stored as TVM NULL.
But since map is a dedicated type, it must be checked with isEmpty().
Nullable maps are valid,
then m may be null or may hold either an empty map or a non‑empty map.
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,address,bits256,Point - Invalid:
int,coins,cell
- Valid:
- Values must be serializable
- Valid:
coins,AnyStruct,Cell<AnyStruct> - Invalid:
int,builder
- Valid:
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>
PUSHNULL since TVM NULL represents an empty map.
createMapFromLowLevelDict<K, V>(d: dict): map<K, V>
map.get or related 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>m.findLast(): MapEntry<K, V>
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>
m.iterateNext(current: MapEntry<K, V>): MapEntry<K, V>m.iteratePrev(current: MapEntry<K, V>): MapEntry<K, V>
Augmented hashmaps and prefix dictionaries
These structures are rarely used and are not part of the Tolk type system.- Prefix dictionaries:
import @stdlib/tvm-dictsand 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 asPoint, are automatically serialized and deserialized by the compiler.
How to emulate Set<T> with maps
A suggestion is to use an “empty tensor” for a map:
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?>ormap<K, Point?>. ReturningV?, makes it impossible to distinguish between “key exists but value is null” and “key does not exist”.
Low-level content below, not required for using maps
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
V?, a special struct is returned:
(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: