# Introduction to TON TL-B (Part 1)
[TL-B](https://docs.ton.org/develop/data-formats/tl-b-language) (Type Language - Binary) is a fundamental cornerstone of [TON](https://ton.org/), and understanding it is crucial for comprehending the TON blockchain. However, the [official documentation](https://docs.ton.org/develop/data-formats/tl-b-language) is not very easy to understand. This series of articles will introduce readers to the TL-B language, as well as related concepts such as [Cells and Bags of Cells](https://docs.ton.org/develop/data-formats/cell-boc). This is the first article, in which we introduce the basic syntax of the TL-B language.

## What is TL-B
TL-B originates from [Telegram](https://telegram.org/)'s [TL](https://docs.ton.org/develop/data-formats/tl) language, so if you are familiar with TL language, learning TL-B language will be relatively easier. However, reading this article does not require an understanding of TL language; you just need to know that many concepts of TL-B come from TL language.
As the name suggests, the TL (or TL-B) language is used to define **Type**s. A definition written in TL language for a certain Type is called the **Schema** of that Type. While defining the structure of a certain Type, TL also defines how to serialize/deserialize instances of this Type. During serialization/deserialization, TL processes data in units of 4 bytes. TL-B, on the other hand, processes data in units of bits, which is why it is called TL-Binary. In other words, TL processes byte streams, while TL-B processes bit streams. So, TL-B can produce more compact data.
Readers familiar with [Protobuf](https://protobuf.dev/) may notice that TL and TL-B bear some resemblance to Protobuf. When using such [IDL](https://en.wikipedia.org/wiki/Interface_description_language) languages to define structures and formats in programming, **code generation** is often required. There are TL-B code generators for [C++](https://github.com/ton-blockchain/ton/blob/master/crypto/tl/tlbc-gen-cpp.cpp), [Python](https://github.com/disintar/ton/blob/master/crypto/tl/tlbc-gen-py.cpp) and [TypeScript](https://github.com/ton-community/tlb-codegen) languages. In this article, we will write some examples using TL-B, then use the [tlb-codegen](https://github.com/ton-community/tlb-codegen) library to generate TypeScript code, and finally use the generated code to write tests to help us understand the TL-B language.
## Basic Structure
Types defined in the TL-B language have such a basic structure, which looks like an equation:
```tlb
constructor#prefix field1:type1 field2:type2 ... fieldN:typeN = StructName;
```
Let's take a look at an actual example:
```tlb
point3d#1234abcd x:u32 y:u32 z:u32 = Point3D;
```
Like most programming languages, whitespace characters are not that important in TL-B language. Moreover, TL-B can use [comments](https://docs.ton.org/develop/data-formats/tl-b-language#comments) similar to those in C++ language. The above example is relatively simple, so it is written in one line. If we format it into multiple lines and add some comments, it will look very similar to the structure definition in common C-series programming languages:
```tlb
point3d#1234abcd // constructor
x:u32 // field1, 32 bits unsigned integer
y:u32 // field2, 32 bits unsigned integer
z:u32 // field3, 32 bits unsigned integer
= Point3D;
```
## Constructors
In the previous example, `point3d#1234abcd` is called [Constructor](https://docs.ton.org/develop/data-formats/tl-b-language#constructors) in TL-B. But it actually has little to do with constructors in ordinary programming languages. This constructor is divided into two parts: `point3d` is the constructor name, and `#1234abcd` is a 4-byte hexadecimal integer called **Tag**. If the `#` is removed, the remaining digits of tag are called the **Prefix Code**. Here are some examples:
```tlb
a#1a2b3c4d x:uint32 = A;
b#1234 x:uint32 = B;
c#abc x:uint32 = C;
```
Besides `#`, we can also use `$` followed by binary digits to define a tag. Here are some examples:
```tlb
d$01010101 x:uint32 = D;
e$1010 x:uint32 = E;
```
We can also omit the prefix code or tag, allowing the TL-B code generator to automatically calculate the tag for us:
```tlb
f x:uint32 = F;
g# x:uint32 = G; // ⚠️ tlb-codegen can not handle this
```
We can also completely omit the prefix code or tag by writing an underscore, so TL-B will not serialize the tag:
```tlb
h#_ x:uint32 = H;
i$_ x:uint32 = I;
_ x:uint32 = J;
```
To test code generation and data serialization, we put the previous TL-B schemas in a file named `tag.tlb`, and then we can use [tlb-codegen cli](https://github.com/ton-community/tlb-codegen?tab=readme-ov-file#cli) to generate a `tag.ts` file, which can then be used to write tests (the `test()` function will be provided at the end of the article):
```ts
import assert from 'node:assert';
import * as tagTlb from './tlb/tag';
function testTag() {
test('tag', tagTlb, [
[{kind: 'A', x: 0x1234}, '0x1A2B3C4D_00001234'],
[{kind: 'B', x: 0x1234}, '0x1234_00001234'],
[{kind: 'C', x: 0x1234}, '0xABC_00001234'],
[{kind: 'D', x: 0x1234}, '0x55_00001234'],
[{kind: 'E', x: 0x1234}, '0xA_00001234'],
[{kind: 'F', x: 0x1234}, '0x1C0AAAB2_00001234'],
[{kind: 'H', x: 0x1234}, '0x00001234'], // no tag
[{kind: 'I', x: 0x1234}, '0x00001234'], // no tag
[{kind: 'J', x: 0x1234}, '0x00001234'], // no tag
]);
}
```
If we don't provide the tag, the code generator will automatically calculate it for us. By processing the entire schema slightly (removing extra whitespace, parentheses, semicolons, etc.) and then calculating it's [CRC32](https://docs.ton.org/develop/data-formats/crc32) checksum, we can obtain the tag. We can use [online tools](https://emn178.github.io/online-tools/crc/) to calculate CRC32 for verification. But here we write a test to verify it:
```ts
import * as crc32 from "crc-32";
function calcTag(schema: string) {
const n = crc32.str(schema);
return (BigInt(n) & 0x7FFFFFFFn).toString(16).padStart(8, '0');
}
function testCRC32() {
assert.equal(calcTag('f x:uint32 = F'), '1c0aaab2');
}
```
## Built-in Types
TL-B comes with some [basic data types](https://docs.ton.org/develop/data-formats/tl-b-language#built-in-types), including unsigned integers, signed integers, bit strings of various lengths, as well as Any and Cell. This article introduces signed/unsigned integers and bit strings. Any and Cell will be introduced in later articles.
### Nat (Unsigned Integer)
In TL-B, unsigned integers are called natural numbers, abbreviated as **Nat**. X-bit Nat is represented by two hash symbols followed by N: `## N`, for example, 16-bit Nat is denoted as `## 16`. To enhance readability, we can enclose it in parentheses, like this: `(##16)`. Here are some examples of Nats of various lengths:
```
_ x:(## 1) = U1;
_ x:(## 2) = U2;
_ x:(## 3) = U3;
_ x:(## 4) = U4;
_ x:(## 8) = U8;
_ x:(## 16) = U16;
_ x:(## 32) = U32;
_ x:(## 64) = U64;
_ x:(## 128) = U128;
_ x:(## 256) = U256;
_ x:(## 257) = U257;
```
Below is a serialization test written using the generated code. As you can see, an N-bit Nat indeed occupies N bits after serialization:
```ts
function testNat1() {
test('nat1', intTlb, [
[{kind: 'U1', x: 0}, '0b0'],
[{kind: 'U1', x: 1}, '0b1'],
[{kind: 'U2', x: 1}, '0b01'],
[{kind: 'U2', x: 2}, '0b10'],
[{kind: 'U3', x: 3}, '0b011'],
[{kind: 'U3', x: 4}, '0b100'],
[{kind: 'U4', x: 5}, '0b0101'],
[{kind: 'U8', x: 0x8A}, '0x8A'],
[{kind: 'U16', x: 0x1A6B}, '0x1A6B'],
[{kind: 'U32', x: 0x3A2B1C}, '0x003A2B1C'],
[{kind: 'U64', x: 0x123456789A}, '0x00000012_3456789A'],
[{kind: 'U128', x: 0x123456789ABC}, '0x00000000_00000000_00001234_56789ABC'],
[{kind: 'U256', x: 0x123456789ABCDE}, '0x00000000_00000000_00000000_00000000_00000000_00000000_00123456_789ABCDE'],
]);
}
```
There are multiple ways to write a Nat type, all of which are equivalent. Additionally, the 32-bit Nat might be the most commonly used one, so TL-B provides it with the shortest alias: `#`. Below example shows various ways to write 32-bit Nat type:
```
_ x:# = U32a;
_ x:(## 32) = U32b;
_ x:uint32 = U32c;
_ x:(uint 32) = U32d;
```
Below is a serialization test written using the generated code. It can be seen that the above notations are all equivalent:
```ts
function testNat2() {
test('nat2', intTlb, [
[{kind: 'U32a', x: 0x3A2B1C}, '0x003A2B1C'],
[{kind: 'U32b', x: 0x3A2B1C}, '0x003A2B1C'],
[{kind: 'U32c', x: 0x3A2B1C}, '0x003A2B1C'],
[{kind: 'U32d', x: 0x3A2B1C}, '0x003A2B1C'],
]);
}
```
Besides directly specifying the number of bits of a Nat type, we can also specify the maximum value of it, and let the code generator calculate how many bits are needed. Here are some examples:
```
_ x:(#< 4) = U4lt;
_ x:(#<= 4) = U4le;
_ x:(#< 8) = U8lt;
_ x:(#<= 8) = U8le;
_ x:(#< 16) = U16lt;
_ x:(#<= 16) = U16le;
_ x:(#< 32) = U32lt;
_ x:(#<= 32) = U32le;
_ x:(#< 64) = U64lt;
_ x:(#<= 64) = U64le;
```
Below is a serialization test written using the generated code:
```ts
function testNat3() {
test('nat3', intTlb, [
[{kind: 'U4lt', x: 3}, '0b11'],
[{kind: 'U4le', x: 4}, '0b100'],
[{kind: 'U8lt', x: 7}, '0b111'],
[{kind: 'U8le', x: 8}, '0b1000'],
[{kind: 'U16lt', x: 15}, '0b1111'],
[{kind: 'U16le', x: 16}, '0b10000'],
[{kind: 'U32lt', x: 31}, '0b11111'],
[{kind: 'U32le', x: 32}, '0b100000'],
[{kind: 'U64lt', x: 63}, '0b111111'],
[{kind: 'U64le', x: 64}, '0b1000000'],
]);
}
```
By the way, TL-B gives 256-bit Nat type an alias: [UInt](https://github.com/ton-blockchain/ton/blob/v2024.09/crypto/tl/tlbc.cpp#L2510). That is to say, the following ways of writing are all equivalent:
```tlb
_ x:(## 256) = U256a;
_ x:uint256 = U256b;
_ x:(uint 256) = U256c;
_ x:Uint = U256d;
```
⚠️ However, in the tlb-codegen library, [Uint](https://github.com/ton-community/tlb-codegen/blob/v1.0.1/src/astbuilder/handle_type.ts#L236) is used instead, and it has been implemented as [257 bits](https://github.com/ton-community/tlb-codegen/blob/v1.0.1/src/astbuilder/handle_type.ts#L239), which seems like a bug.
### Int (Signed Integer)
Corresponding to unsigned integers are signed integers, here are some examples of signed integers:
```
_ x:int4 = I4;
_ x:int8 = I8;
_ x:int16 = I16;
_ x:int32 = I32;
_ x:int64 = I64;
_ x:int128 = I128;
_ x:int256 = I256;
```
Below is a serialization test written using the generated code:
```ts
function testInt() {
test('int', intTlb, [
[{kind: 'I4', x: -4}, '0b1100'],
[{kind: 'I8', x: -8}, '0b11111000'],
[{kind: 'I16', x: -16}, '0b1111111111110000'],
[{kind: 'I32', x: -32}, '0xFFFFFFE0'],
[{kind: 'I64', x: -64}, '0xFFFFFFFFFFFFFFC0'],
[{kind: 'I128', x: -128}, '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80'],
[{kind: 'I256', x: -256}, '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00'],
]);
}
```
Signed integer types can also be written in the form of `int N`. Additionally, since [TVM](https://docs.ton.org/learn/tvm-instructions/tvm-overview#tvm-is-a-stack-machine) directly supports 257-bit signed integers, TL-B has also given `int257` a shorter alias: `Int`. The following example shows three ways to write `int257` type:
```
_ x:int257 = I257a;
_ x:(int 257) = I257b;
_ x:Int = I257c;
```
### Bits (Bit String)
Similar to **Nat** and **Int** types, the **Bits** type also has two notations: `bitsN` and `bits N`. Below are some examples of the notation without spaces:
```
_ x:bits1 = B1;
_ x:bits2 = B2;
_ x:bits3 = B3;
_ x:bits10 = B10;
_ x:bits20 = B20;
_ x:bits160 = B160;
```
Since a TON [Cell](https://docs.ton.org/develop/data-formats/cell-boc#cell) can hold up to 1023 bits, TL-B gives `bits1023` a shorter alias: `Bits`. The following example shows three ways to write `bits1023` type:
```
_ x:Bits = B1023a;
_ x:bits1023 = B1023b;
_ x:(bits 1023) = B1023c;
```
⚠️ However, the serialization code generated by tlb-codegen doesn't seem to be very good, and you need to provide an appropriate BitString yourself. Below is the test code:
```ts
function testBits() {
test('bits', bitsTlb, [
[{kind: 'B1', x: new BitString(Buffer.from('1234', 'hex'), 0, 16)}, '0b1'],
[{kind: 'B2', x: new BitString(Buffer.from('1234', 'hex'), 0, 16)}, '0x1234'],
[{kind: 'B3', x: new BitString(Buffer.from('1234', 'hex'), 0, 16)}, '0x1234'],
[{kind: 'B10', x: new BitString(Buffer.from('1234', 'hex'), 0, 16)}, '0x1234'],
[{kind: 'B20', x: new BitString(Buffer.from('1234', 'hex'), 0, 16)}, '0x1234'],
[{kind: 'B160', x: new BitString(Buffer.from('1234', 'hex'), 0, 16)}, '0x1234'],
[{kind: 'B1023a', x: new BitString(Buffer.from('1234', 'hex'), 0, 16)}, '0x1234'],
]);
}
```
### Dynamic Nat
The various Nat and Int types introduced earlier all had their lengths known at the codegen stage. TL-B also supports Nat and Int types whose lengths can only be determined at runtime. Here is an [example](https://docs.ton.org/develop/data-formats/tl-b-language#nat-fields-usage-for-parametrized-types):
```tlb
_ a:(## 8) b:(## a) = A;
```
The type `A` defined in the above example has two fields, both of which are of type Nat, but the length of field `b` is determined by the value of field `a`. ⚠️ However, [tlb-codegen](https://github.com/ton-community/tlb-codegen) does not support this kind of writing.
Let's summarize the various notations and aliases for three kinds of built-in types: Uint, Int, and Bits.
| Built-in Type | Parametrized | Non-Parametrized | Special | Type Aliases |
| ----------------- | ------------ | ---------------- | ------- | ------------------------------------ |
| Unsigned Integers | `uint N` | `uintN` | `## N` | `UInt` = `uint 256`, `#` = `uint 32` |
| Signed Integers | `int N` | `intN` | | `Int` = `int 257` |
| Bit Strings | `bits N` | `bitsN` | | `Bits` = `bits 1023` |
## Constraints
Besides specifying the type of a field, TL-B also allows us to add some [constraints](https://docs.ton.org/develop/data-formats/tl-b-language#constraints) to the field. The constraints follow the type and are enclosed by curly braces. Therefore, we can modify the basic structure of TL-B types as follows:
```tlb
constructor#prefix
field1:type-expr1
field2:type-expr2
fieldN:type-expr3
... // other fields
= StructName;
```
The above `type-expr` can be either a simple type or a type with constraints. In the constraints, we can use `=`, `<`, `<=`, `>`, and `>=` operators to limit the value of the field. Here are some examples:
```
_ x:# { x = 100 } = EQ1;
_ x:# { x < 100 } = LT1;
_ x:# { x <= 100 } = LE1;
_ x:# { x > 100 } = GT1;
_ x:# { x >= 100 } = GE1;
```
If we violate these constraints, an error will be reported during serialization or deserialization. Below is a serialization test written with the generated code, where we test both normal and exceptional cases:
```ts
function testConstraints1() {
test('constraints1', constraintsTlb, [
[{kind: 'EQ1', x: 100}, '0x00000064'],
[{kind: 'LT1', x: 99}, '0x00000063'],
[{kind: 'LE1', x: 100}, '0x00000064'],
[{kind: 'GT1', x: 101}, '0x00000065'],
[{kind: 'GE1', x: 100}, '0x00000064'],
[{kind: 'EQ1', x: 99}, `Error: Condition (eQ1.x == 100) is not satisfied while loading "EQ1" for type "EQ1"`],
[{kind: 'LT1', x: 100}, `Error: Condition (lT1.x < 100) is not satisfied while loading "LT1" for type "LT1"`],
[{kind: 'LE1', x: 101}, `Error: Condition (lE1.x <= 100) is not satisfied while loading "LE1" for type "LE1"`],
[{kind: 'GT1', x: 100}, `Error: Condition (gT1.x > 100) is not satisfied while loading "GT1" for type "GT1"`],
[{kind: 'GE1', x: 99}, `Error: Condition (gE1.x >= 100) is not satisfied while loading "GE1" for type "GE1"`],
]);
}
```
Of course, in addition to comparing with constants, we can also compare two fields. Here are some examples:
```
_ x:# y:# { x = y } = EQ2;
_ x:# y:# { x < y } = LT2;
_ x:# y:# { x <= y } = LE2;
_ x:# y:# { x > y } = GT2;
_ x:# y:# { x >= y } = GE2;
```
Below is a serialization test written with the generated code, where we test both normal and exceptional cases:
```ts
function testConstraints2() {
test('constraints2', constraintsTlb, [
[{kind: 'EQ2', x: 100, y: 100}, '0x00000064_00000064'],
[{kind: 'LT2', x: 99, y: 100}, '0x00000063_00000064'],
[{kind: 'LE2', x: 100, y: 100}, '0x00000064_00000064'],
[{kind: 'GT2', x: 101, y: 100}, '0x00000065_00000064'],
[{kind: 'GE2', x: 100, y: 100}, '0x00000064_00000064'],
[{kind: 'EQ2', x: 99, y: 100}, `Error: Condition (eQ2.x == eQ2.y) is not satisfied while loading "EQ2" for type "EQ2"`],
[{kind: 'LT2', x: 100, y: 100}, `Error: Condition (lT2.x < lT2.y) is not satisfied while loading "LT2" for type "LT2"`],
[{kind: 'LE2', x: 101, y: 100}, `Error: Condition (lE2.x <= lE2.y) is not satisfied while loading "LE2" for type "LE2"`],
[{kind: 'GT2', x: 100, y: 100}, `Error: Condition (gT2.x > gT2.y) is not satisfied while loading "GT2" for type "GT2"`],
[{kind: 'GE2', x: 99, y: 100}, `Error: Condition (gE2.x >= gE2.y) is not satisfied while loading "GE2" for type "GE2"`],
]);
}
```
In addition to comparison operations, we can also perform addition and multiplication calculations (note that there are no subtraction and division calculations). Here are some examples:
```
_ x:# y:# { x = y + 100 } = OP1;
_ x:# y:# { x = y * 2 } = OP2;
_ x:# y:# { x = y * 2 + 100 } = OP3;
_ x:# y:# { x * 2 = y + 100 } = OP4;
```
Below is the test code:
```ts
function testConstraints3() {
test('constraints3', constraintsTlb, [
[{kind: 'OP1', x: 300, y: 200}, '0x0000012C_000000C8'],
[{kind: 'OP2', x: 400, y: 200}, '0x00000190_000000C8'],
[{kind: 'OP3', x: 500, y: 200}, '0x000001F4_000000C8'],
[{kind: 'OP4', x: 150, y: 200}, '0x00000096_000000C8'],
[{kind: 'OP1', x: 299, y: 200}, `Error: Condition (oP1.x == (oP1.y + 100)) is not satisfied while loading "OP1" for type "OP1"`],
[{kind: 'OP2', x: 399, y: 200}, `Error: Condition (oP2.x == (oP2.y * 2)) is not satisfied while loading "OP2" for type "OP2"`],
[{kind: 'OP3', x: 499, y: 200}, `Error: Condition (oP3.x == ((oP3.y * 2) + 100)) is not satisfied while loading "OP3" for type "OP3"`],
[{kind: 'OP4', x: 149, y: 200}, `Error: Condition ((oP4.x * 2) == (oP4.y + 100)) is not satisfied while loading "OP4" for type "OP4"`],
]);
}
```
Besides `=`, `<`, `<=`, `>`, `>=`, `+` and `*`, TL-B also supports `?`, `.`, and `~` operators, which we will introduce later.
## Implicit Fields
In the previous examples, we introduced how to define **explicit** fields of a Type. That is, these fields will be serialized and deserialized. TL-B also allows us to define [implicit fields](https://docs.ton.org/develop/data-formats/tl-b-language#implicit-fields) by put the field definition inside curly braces. Implicit fields are deduced from the preceding explicit fields and constraints. Let's take a look at an example (note the [~ operator](https://docs.ton.org/develop/data-formats/tl-b-language#negate-operator-)):
```tlb
_ a:(## 32) {x:#} { ~x = a + 1 } = A;
```
Implicit fields are crucial for defining[ parameterized types](https://docs.ton.org/develop/data-formats/tl-b-language#parametrized-types-1), as we'll explain in the next section.
## Parameterized Types
We have introduced various lengths of Nat, Int, and Bits types earlier. Taking Nat as an example, we can actually view `uint N` as a built-in **parameterized type**, with `uint8`, `uint16`, `uint32`, etc., being its specializations.
In fact, through **implicit fields**, TL-B allows us to define our own parameterized types. When defining parameterized types, it is common to write the implicit fields at the beginning, so we modify the overall structure of TL-B Types again:
```tlb
constructor#prefix
{ implicit-field1:type1 },
{ implicit-field2:type2 },
... // other implicit fields
field1:type-expr1
field2:type-expr2
fieldN:type-expr3
... // other fields
= StructName X Y m n ...;
```
Next, let's look at how to define a parameterized type through an example of Point2D. First, let's see how a regular Point2D might be defined:
```tlb
_ x:# y:# = Point2Da;
_ p:Point2Da = A;
```
In the above example, we hardcoded the width of the coordinates of Point2D to 32 bits. This might not be ideal, so we can make the width of the coordinates a parameter of type Nat. This way, we can flexibly define points of various widths:
```tlb
_ {n:#} x:(## n) y:(## n) = Point2Db n;
_ p:(Point2Db 16) = B1;
_ p:(Point2Db 32) = B2;
```
If we want x and y coordinates to have different widths, we can introduce another Nat type parameter:
```tlb
_ {m:#} {n:#} x:(## m) y:(## n) = Point2Dc m n;
_ p:(Point2Dc 32 64) = C;
```
If we want x and y coordinates to be integers of any same type, then we simply replace Nat type with any type (`Type`):
```tlb
_ {U:Type} x:U y:U = Point2Dd U;
_ p:(Point2Dd (## 16)) = D1;
_ p:(Point2Dd (## 32)) = D2;
```
If we want x and y coordinates to be integers of any type, then we add an additional parameter of any type:
```tlb
_ {X:Type} {Y:Type} x:X y:Y = Point2De X Y;
_ p:(Point2De (## 32) (## 64)) = E1;
_ p:(Point2De (uint32) (int32)) = E2;
```
We can also mix Nat and `Type` parameters, for example:
```tlb
_ {X:Type} {n:#} x:X y:(## n) = Point2Df X n;
_ p:(Point2Df (## 32) 64) = F;
```
## Advanced Types
We've covered the basics of TL-B syntax. Now, let's explore some type definitions in [block.tlb](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb) to enhance our understanding. Some of those types are also introduced in [TL-B types document](https://docs.ton.org/develop/data-formats/tl-b-types).
### Bit
Actually, we already have many ways to define a 1-bit field, for example:
```tlb
_ x:(## 1) = B1a;
_ x:uint1 = B1b;
_ x:bits1 = B1c;
```
However, for clarity, it can still be explicitly [defined](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb#L14):
```
bit$_ (## 1) = Bit;
_ x:Bit = B1d;
```
### Bool
Bool is essentially 1-bit integer, but for clarity, we can explicitly [define](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb#L4) it:
```
bool_false$0 = Bool;
bool_true$1 = Bool;
_ x:Bool = A;
```
### Varint
The value of some fields is very small most of the time, but occasionally very large. In this case, using a variable-length integer will be more compact. Variable-length integers usually need to serialize the length of the integer first, and then serialize the integer value. Let's look at the [definition](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb#L112) of a signed and unsigned variable-length integer:
```
var_uint$_ {n:#} len:(#< n) value:(uint (len * 8)) = VarUInteger n; // VarUInteger<n>
var_int$_ {n:#} len:(#< n) value:(int (len * 8)) = VarInteger n; // VarInteger<n>
```
Taking varuint as an example, `VarUInteger 8` represents an variable length unsigned integer that does not exceed 8 bytes. Below is a test written with the generated code:
```go
function testVarint() {
test('varint', varintTlb, [
[{kind: 'U16', x: 0x12n}, '0b0001_00010010'],
[{kind: 'U32', x: 0x1234n}, '0b00010_00010010_00110100'],
[{kind: 'U64', x: 0x123456n}, '0b000011_00010010_00110100_01010110'],
[{kind: 'I16', x: 0x12n}, '0b0000000000000010_00000000_00010010'],
[{kind: 'I16', x: -0x12n}, '0b0000000000000010_11111111_11101110'],
[{kind: 'I32', x: 0x1234n}, '0b00000000000000000000000000000011_00000000_00010010_00110100'],
[{kind: 'I32', x: -0x1234n}, '0b00000000000000000000000000000011_11111111_11101101_11001100'],
[{kind: 'I64', x: 0x123456n}, '0b0000000000000000000000000000000000000000000000000000000000000100_00000000_00010010_00110100_01010110'],
[{kind: 'I64', x: -0x123456n}, '0b0000000000000000000000000000000000000000000000000000000000000100_11111111_11101101_11001011_10101010'],
]);
}
```
⚠️ As you can see, varuint indeed achieves a good compact effect. But varint seems to have the opposite effect.
### Either
The [Either](https://docs.ton.org/develop/data-formats/tl-b-types#either) type determines which of the two values to serialize through one bit. Below is the [definition](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb#L10) of `Either` type:
```
// Either<X, Y>
left$0 {X:Type} {Y:Type} value:X = Either X Y;
right$1 {X:Type} {Y:Type} value:Y = Either X Y;
_ x:(Either (## 8) (## 16)) = A;
```
Below is a test written with the generated code:
```ts
function testEither() {
test('either', typesTlb, [
[{kind: 'A', x: {kind: 'Either_left', value: 0x12}}, '0b0_00010010'],
[{kind: 'A', x: {kind: 'Either_right', value: 0x34}}, '0b1_00000000_00110100'],
]);
}
```
### Maybe
The [Maybe](https://docs.ton.org/develop/data-formats/tl-b-types#maybe) type decides whether to serialize a value through one bit. Below is the [definition](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb#L9) of the `Maybe` type:
```
// Maybe<X>
nothing$0 {X:Type} = Maybe X;
just$1 {X:Type} value:X = Maybe X;
_ x:(Maybe (## 24)) = B1;
```
Below is a test written with the generated code:
```ts
function testMaybe() {
test('maybe', typesTlb, [
[{kind: 'B1', x: {kind: 'Maybe_nothing'}}, '0b0'],
[{kind: 'B1', x: {kind: 'Maybe_just', value: 0x1234}}, '0b1_00000000_00010010_00110100'],
]);
}
```
Actually, the effect of the `Maybe` type can also be achieved through the `?` operator. The `B2` defined below should be equivalent to the `B1` defined above:
```
_ x:(## 1) y:x?(## 24) = B2;
```
⚠️ However, at least the code generated by [tlb-codegen](https://github.com/ton-community/tlb-codegen) is not the same, which we will not go into detail here. Moreover, using `Maybe` type makes the intention clearer.
### Both
When we want to emphasize that two values form a pair, we can use the [Both](https://docs.ton.org/develop/data-formats/tl-b-types#both) type. Below is the [definition](https://github.com/ton-blockchain/ton/blob/master/crypto/block/block.tlb#L12) of the `Both` type:
```
// Both<X, Y>
pair$_ {X:Type} {Y:Type} first:X second:Y = Both X Y;
_ x:(Both (## 32) (## 40)) = C;
```
Below is a test written with the generated code:
```ts
function testBoth() {
test('both', typesTlb, [
[{kind: 'C', x: {kind: 'Both', first: 0x1234, second: 0x5678}}, '0x00001234_0000005678'],
[{kind: 'C', x: {kind: 'Both', first: 0x5678, second: 0x1234}}, '0x00005678_0000001234'],
]);
}
```
## Summary
In this article, we first briefly introduced the purpose and basic syntax of the TL-B language. Then we introduced the built-in types Nat, Int, and Bits. Next, we discussed constraints, implicit fields, and how to define parameterized types. Finally, we introduced advanced types such as Varuint, Either, and Maybe. In the next article, we will introduce Cell and Bag of Cells.
Buy me a cup of coffee if this article has been helpful to you:
* EVM: `0x8f7BEE940b9F27E8d12F6a4046b9EC57c940c0FA`
* TON: `UQBk1flhLnRsAebsYFnvt8HUwI4s_dbG7w3AzpyH5SbIHq_S`
## Appendix
Here is the complete code for the `test()` function:
```ts
function test(_case: string, tlb: any, testCases: [any, string][]) {
console.log('test:', _case);
for (const [obj, expectedStr] of testCases) {
const storeFunc = tlb['store' + obj['kind']];
const builder = storeFunc(obj);
if (expectedStr.startsWith('Error')) {
try {
toHexStr(builder);
} catch (err) {
assert.equal(err + '', expectedStr);
}
} else {
const actual = expectedStr.startsWith('0x')
? toHexStr(builder)
: toBinStr(builder);
assert.equal(actual, remove_(expectedStr));
}
}
}
function remove_(s: string) {
return s.replaceAll('_', '');
}
function toHexStr(f: (builder: Builder) => void) {
const builder = beginCell();
f(builder);
const cell = builder.endCell();
return '0x' + cell.bits.toString();
}
function toBinStr(f: (builder: Builder) => void) {
const builder = beginCell();
f(builder);
const cell = builder.endCell();
const cellBits = cell.bits.toString();
return '0b' + bitStrToBin(cellBits);
}
function bitStrToBin(s: string) {
let binStr = '';
while (s.length > 0) {
if (s == '_') {
return removePaddingBits(binStr);
}
binStr += Number('0x0' + s[0]).toString(2).padStart(4, '0');
s = s.slice(1);
}
return binStr;
}
function removePaddingBits(s: string) {
if (s.endsWith('1')) {
return s.slice(0, -1);
}
if (s.endsWith('10')) {
return s.slice(0, -2);
}
if (s.endsWith('100')) {
return s.slice(0, -3);
}
return s;
}
```