lazy keyword – the compiler tracks which fields are accessed and loads only those fields, skipping the rest.
In practice, prefer lazy T.fromCell() over T.fromCell().
lazy usage
Consider a Storage struct in a wallet:
Storage.load() unpacks the cell, loads all fields, performs consistency checks, etc.
lazy Storage.load() does not load all fields. Unused fields are skipped:
lazy usage of referenced cells
Consider the NFT collection:
content and then get commonKey from it:
- Skipping
addressanduint64is unnecessary. Accessing a reference does not require skipping preceding fields. - To read
commonKeyfromcontent, load the cell withlazy.
p: Cell<Point> does not allow direct access to p.x. The cell must be loaded first using either Point.fromCell(p) or p.load(). Both work with lazy.
lazy matching
A union type such as an incoming message can be read with lazy:
lazy applied to unions:
- No union is allocated on the stack upfront; matching and loading are deferred until needed.
matchoperates by inspecting the slice prefix (opcode).- Within each branch, the compiler inserts loading points and skips unused fields, as it does for structs.
lazy matching avoids unnecessary stack operations.
lazy matching and else
match with lazy on a union operates by inspecting the prefix. Any unmatched case falls into the else branch.
else, unpacking throws error 63 by default, which is controlled by the throwIfOpcodeDoesNotMatch option in fromSlice. The else branch allows inserting any custom logic.
else in a type-based match is allowed only with lazy because matching uses prefixes. Without lazy, the union is matched normally and an else branch is not allowed.
Partial updating
Thelazy keyword also applies when writing data back.
Example:
Load a storage, use its fields for assertions, update one field, and save it back:
toCell() does not save all fields of the storage since only seqno is modified. Instead, after loading seqno, the compiler saves an immutable tail and reuses it when writing back:
How does lazy skip unused fields?
When several consecutive fields are unused, the compiler tries to group them. This works for fixed-size types such as intN or bitsN:
coins, cannot be grouped and cannot be skipped with one instruction – TVM has no instruction for that. The only option is to load the value and ignore it.
The same applies to address. Even though it occupies 267 bits, the value should be validated even when unused; otherwise, binary data could be decoded incorrectly.
For these types, lazy does only “load and ignore”.
In practice, intN types are common, so grouping has an effect. The trick “access a ref without skipping any data” also works.
What are the disadvantages of lazy?
In terms of gas consumption, lazy fromSlice is equal to or cheaper than regular fromSlice. When all fields are accessed, it loads them one by one, the same way as the non-lazy version. There is a difference unrelated to gas consumption:
- If a slice is small or contains extra data,
fromSlicethrows. lazypicks only the requested fields and handles partially invalid input. For example:
p.x is accessed, an input of FF (8 bits) is acceptable even though y is missing. Similarly, FFFF0000 (16 bits of extra data) is also acceptable, as lazy ignores any data that is not requested.