To set or replace the value under a key call the .set()method, which is accessible for all maps.
Get values, .get()
To check if a key is found in the map by calling the .get()method, which is accessible for all maps. This will return null if the key is missing, or the value if the key is found.
Replace values, .replace()
Available since Tact 1.6
To replace the value under a key, if such a key exists, use the .replace()method. It returns true on successful replacement and false otherwise.
If the given value is null and the key exists, the entry will be deleted from the map.
Replace and get old value, .replaceGet()
Available since Tact 1.6
Like .replace(), but instead of returning a Bool it returns the old (pre-replacement) value on successful replacement and null otherwise.
If the given value is null and the key exists, the entry will be deleted from the map.
Delete entries, .del()
To delete a single key-value pair (single entry), use the .del()method. It returns true in the case of successful deletion and false otherwise.
To delete all the entries from the map, re-assign the map using the emptyMap() function:
With this approach all previous entries of the map are completely discarded from the contract even if the map was declared as its persistent state variable. As a result, assigning maps to emptyMap()does not inflict any hidden or sudden storage fees.
Check if entry exists, .exists()
Available since Tact 1.5
The .exists()method on maps returns true if the value under the given key exists in the map and false otherwise.
Check if empty, .isEmpty()
The .isEmpty()method on maps returns true if the map is empty and false otherwise:
Compare with .deepEquals()
Available since Tact 1.5
The .deepEquals()method on maps returns true if all entries of the map match corresponding entries of another map, ignoring possible differences in the underlying serialization logic. Returns false otherwise.
Using .deepEquals() is very important in cases where a map comes from the third-party source that doesn’t provide any guarantees about the serialization layout. For one such example, consider the following code:
There, both maps are formed manually and both contain the same key-value pair. If you were to send both of those maps in a message to the Tact contract, and then compare them with .deepEquals() and equality operator ==, the former would produce true because both maps have the same entry, while the latter would produce false, because it only does the shallow comparison of map hashes. And those differ since the maps are serialized differently.
Convert to a Cell, .asCell()
Use .asCell()method on maps to convert all their values to a Cell type. Be mindful, that Cell type is able to store up to 1023 bits, so converting larger maps to the Cell will result in error.
As an example, this method is useful for sending small maps directly in the body of the reply:
Traverse over entries
To iterate over map entries there is a foreach loop statement:
Note, that it’s also possible to use maps as simple arrays if you define a map<Int, V> with an Int type for the keys, any allowed V type for values and keep track of the number of items in the separate variable:
It’s often useful to set an upper-bound restriction on such maps, so that you don’t hit the limits.
While maps can be convenient to work with on a small scale, they cause a number of issues if the number of items is unbounded and map can significantly grow in size:
As the upper bound of the smart contract state size is around 65000 items of type Cell, it constrains the storage limit of maps to be about 30000 key-value pairs for the whole contract.
The more entries you have in a map, the bigger compute fees you’ll get. Thus, working with large maps makes compute fees tough to predict and manage.
Using a large map in a single contract doesn’t allow to distribute its workload. Hence, it can make the overall performance much worse compared to using a smaller map and a bunch of interacting smart contracts.
To resolve such issues you can set an upper-bound restriction on a map as a constant and check against it every time you’re setting a new value to the map:
If you still need a large map or an unbound (infinitely large) map, it’s better to architect your smart contracts according to the asynchronous and actor-based model of TON blockchain. That is, to use contract sharding and essentially make the whole blockchain a part of your map(s).