Introduction
In this article, we’ll dive deep into the concept of implicit fields in the TL-B language — a critical feature for understanding TON blockchain data structures.
When developing smart contracts on TON, TL-B schemas are used to define standardized cell layouts such as CommonMsgInfo
, StateInit
, and many others. Among all aspects of TL-B, implicit fields and their value-assignment rules are some of the trickiest to master.
Understanding implicit fields is essential for:
- Correctly interpreting
.tlb
schemas - Implementing serialization and deserialization logic
- Writing tools and parsers for TON data
In this article, we’ll explore:
- What implicit fields are and how they are defined
- Where and why they are used
- The two main usage scenarios (parameterized types and derived values)
- Constraints and the rules for constraint expressions
- Best practices and naming conventions
Before you continue, it’s worth reviewing the official TL-B documentation:
Most examples in this article are drawn from the official TON TL-B schemas and documentation:
-
block.tlb
— defines the structure of blocks and block headers -
hashmap.tlb
— defines hashmaps and their serialization in TL-B -
boc.tlb
— defines the Bag of Cells (BOC) container format
For deeper understanding of how these schemas are used in TON layouts, consult the official documentation:
Note: I’ll use TypeScript-style pseudocode to illustrate TL-B objects.
Understanding Implicit Fields in TL-B
According to the official TL-B documentation:
“Some fields may be implicit. These fields are defined within curly braces
{ }
, indicating that they are not directly serialized. Instead, their values must be deduced from other data, usually the parameters of the type being serialized.”
Before diving deeper, let’s clarify a term used throughout this article — the resulting bitstring (or simply bitstring).
This is the binary string produced after serializing an object according to its TL-B definition. It represents the actual bits written into a cell and is used to reconstruct the object during deserialization.
For example:
const A = {
x: 2,
y: 3
};
and the corresponding TL-B combinator declaration:
constructor1$10 x:(## 4) y:(## 6) = A;
Serialization proceeds as follows:
- The first two bits
"10"
represent the constructor tag. - Then we encode
x = 2
using 4 bits:0010
. - Then we encode
y = 3
using 6 bits:000011
.
The resulting bitstring is:
"10" + "0010" + "000011"
→ 100010000011
What Are Implicit Fields?
TL-B has two kinds of fields:
- Explicit fields — values are represented in the resulting bitstring.
- Implicit fields — values are not represented in the bitstring but are derived or passed as parameters.
Implicit fields make TL-B schemas more compact and expressive. They let you describe structural relationships and parameter dependencies without wasting bits on redundant data.
Defining Implicit Fields
Curly braces { }
in TL-B can serve several purposes:
- Defining implicit fields
- Expressing constraints on values
- Declaring computed or returned fields
The general syntax for an implicit field is:
{ field_name : field_type }
Allowed Field Types
-
Natural Numbers (
#
)
{ x:# }
Here, #
represents a natural number (usually a 32-bit unsigned integer).
-
Type Parameters (
Type
)
{ X:Type }
Naming Conventions
Field Type | Naming Convention | Example |
---|---|---|
Natural number | lowercase | {n:#} |
Type parameter | uppercase | {X:Type} |
Constraints in TL-B
In TL-B, constraints are expressions enclosed in { }
that specify conditions a value must satisfy.
They are not serialized — instead, they act as logical rules that implementations must enforce during serialization or deserialization.
Constraints are widely used in TON schemas to ensure consistency between related fields.
1. Boolean constraints
A simple comparison or logical assertion:
flags:(## 8) { flags <= 1 }
This means that the 8-bit field flags
must not exceed 1.
Such constraints are checked during serialization or parsing, ensuring schema consistency.
2. Relational constraints
Constraints can express relationships between multiple fields:
seq_no:# vert_seq_no:# { vert_seq_no >= vert_seqno_incr }
Here, vert_seq_no
must be greater than or equal to vert_seqno_incr
.
No extra bits are stored — this condition just governs valid combinations of values.
3. Derived (computed) constraints
Constraints can also define the value of an implicit field, indicated by a tilde (~
) operator:
{ prev_seq_no:# } { ~prev_seq_no + 1 = seq_no }
This means that prev_seq_no
is implicitly defined by
prev_seq_no = seq_no − 1
(expressed as ~prev_seq_no + 1 = seq_no
, since −
is not allowed in TL-B).
It’s not read from the bitstring — instead, it’s derived from another value.
You can also use multiplication (*
) to express relationships that would otherwise use division.
Since /
is not permitted in TL-B, expressions such as “x = y / 2
” must be rewritten using multiplication:
{ ~x * 2 = y }
This constraint means x = y / 2
, but expressed through the only allowed operation *
.
This pattern appears in TL-B when the number of elements, bits, or cells must be a multiple or divisor of another count.
If the tilde (~
) operator still confuses you, I’ve written a detailed explanation of it and its usage in TL-B here: 👉 Negate Operator (~) in TL-B Explained
4. Fixed-value constraints
Sometimes, a constraint simply fixes a constant:
{ ~len = 8 }
This is equivalent to declaring that the implicit field len
always equals 8.
Summary of Constraint Usage
Type | Purpose | Example |
---|---|---|
Boolean | Limit a value | { flags <= 1 } |
Relational | Relate two fields | { vert_seq_no >= vert_seqno_incr } |
Derived | Define one value from another | { ~prev_seq_no + 1 = seq_no } |
Fixed | Hard-coded constant | { ~len = 8 } |
Constraints are one of the most elegant features of TL-B — they make schemas self-validating, documenting both data layout and expected relationships.
Placement of Implicit Field Definitions
By convention, implicit field definitions — that is, parts like { prev_seq_no:# }
which declare an implicit variable — are usually placed at the beginning of a combinator declaration.
These definitions introduce implicit parameters or values that may later be used by other fields or constraints in the same combinator.
However, this placement is a convention, not a strict requirement — they can appear anywhere within the definition as long as references remain valid.
In contrast, constraints (for example, { ~prev_seq_no + 1 = seq_no }
) typically follow field definitions, since they describe logical relationships among already declared fields.
Example excerpt from block.tlb
:
block_info#9bc7a987 version:uint32
not_master:(## 1)
after_merge:(## 1) before_split:(## 1)
after_split:(## 1)
want_split:Bool want_merge:Bool
key_block:Bool vert_seqno_incr:(## 1)
flags:(## 8) { flags <= 1 }
seq_no:# vert_seq_no:# { vert_seq_no >= vert_seqno_incr }
{ prev_seq_no:# } { ~prev_seq_no + 1 = seq_no }
shard:ShardIdent gen_utime:uint32
start_lt:uint64 end_lt:uint64
gen_validator_list_hash_short:uint32
gen_catchain_seqno:uint32
min_ref_mc_seqno:uint32
prev_key_block_seqno:uint32
gen_software:flags.0?GlobalVersion
master_ref:not_master?^BlkMasterInfo
prev_ref:^(BlkPrevInfo after_merge)
prev_vert_ref:vert_seqno_incr?^(BlkPrevInfo 0)
= BlockInfo;
Here, { prev_seq_no:# }
and { ~prev_seq_no + 1 = seq_no }
demonstrate implicit definition combined with a constraint.
Obtaining Values of Implicit Fields
Suppose we have:
_ {length:#} a:(## length) ...
Where does length
come from?
Implicit fields get their values in two main ways:
- Through type parameters — values are passed when instantiating a type.
- Through constraints or derivation — values are computed or logically determined from other fields.
Example 1: Parameterized Implicit Field
Incorrect definition:
_ val:(## length) = IntVal length; // invalid
Here, length
is not declared, so this schema is invalid.
Correct version:
_ {length:#} val:(## length) = IntVal length;
Now length
is an implicit parameter — it’s passed in rather than serialized.
Example 2: Derived (Calculated) Implicit Field
We can define implicit fields whose values are derived from others using the tilde (~
) operator.
For example:
_ {length:#} val:(## length) { ~length = 8 } = Int8BitVal;
Here { ~length = 8 }
acts as a constraint, meaning “length
must equal 8.”
Implementations can interpret this as a computed or fixed value.
Example 3: Returned Implicit Value
Sometimes a field’s value is returned by another type using the ~
notation:
_ {length:#} val:(## length) = IntVal length ~val;
This means that IntVal
returns its val
, making it accessible to the enclosing type.
We can then use this returned value:
_ {v:#} a:(IntVal 8 ~v) b:(## v) = A;
Here, v
is an implicit field that obtains its value from IntVal 8 ~v
, and that value determines the bit length of b
.
Serialization and Deserialization
It’s important to distinguish between parameterized and derived implicit fields.
- Parameterized fields must be known before serialization or deserialization, since their values define bit sizes or type choices.
- Derived fields are computed during deserialization — they are not read from the bitstring but inferred from other data.
Example: Parameterized Field
_ {x:#} my_val:(## x) = A x;
If we serialize:
const A = { my_val: 5, x: 6 };
We must know x = 6
before serialization to encode the correct number of bits.
During deserialization, we also need x
to know how many bits to read.
Resulting bitstring: 6 bits representing the binary form of 5 → 000101
.
Example: Derived Field
_ {a:#} my_val:(## x) { ~x = a + 1 } = A;
Here, x
is not provided externally.
Instead, during deserialization, after reading a
, we can compute x = a + 1
and use it to read my_val
.
Thus, calculated implicit fields are derived, not deserialized — their values are reconstructed logically.
Summary
Case | Source of Value | Serialization Behavior | Example |
---|---|---|---|
Explicit field | Directly stored in bitstring | Serialized | a:(## 8) |
Implicit (parameterized) | Passed as type parameter | Not serialized | {n:#} value:(## n) |
Implicit (derived) | Computed from other fields | Not serialized | { ~x = a + 1 } |
Constraint | Restriction or relation | Not serialized | { flags <= 1 } |
Returned value | Returned by nested type | Not serialized | Type ~field |
Conclusion
Implicit fields and constraints are key features of TL-B that make TON schemas compact yet expressive.
They allow you to parameterize structures, enforce logical relationships, and define derived values — all without consuming extra bits in serialization.
When implementing parsers or code generators for TL-B, always remember:
- Implicit ≠ serialized — their values come from parameters or computation.
-
Constraint expressions are limited: use
+
and*
only, and express subtraction or division through equivalent additive or multiplicative forms. -
Constraints
{ ~x = expr }
and{ condition }
define logical relationships, not procedural code. - Placement is flexible, though usually at the beginning for clarity.
Mastering implicit fields and constraints will make your understanding of TON’s TL-B language much deeper and your tools more robust.
Top comments (0)