Skip to content
This repository was archived by the owner on Dec 12, 2024. It is now read-only.

Commit 22e9e30

Browse files
authored
feat: Struct & Message declaration syntax + Optionals (#95)
* added motivation to use custom numeric ids to match opcodes
1 parent 1e21374 commit 22e9e30

File tree

3 files changed

+193
-37
lines changed

3 files changed

+193
-37
lines changed

Diff for: pages/book/_meta.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ export default {
66
'--': {
77
type: 'separator',
88
},
9-
types: 'Type system',
9+
types: 'Type system overview',
1010
functions: 'Functions',
1111
statements: 'Statements',
1212
constants: 'Constants',
13+
'defining-types': 'Defining composite types',
1314
receive: 'Receive Messages',
1415
bounced: 'Bounced Messages',
1516
external: 'External Messages',
@@ -37,4 +38,4 @@ export default {
3738
href: 'https://twitter.com/tact_language',
3839
newWindow: true
3940
},
40-
}
41+
}

Diff for: pages/book/defining-types.mdx

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Defining composite types
2+
3+
import { Callout } from 'nextra/components'
4+
5+
Tact supports a number of [primitive data types](/book/types#primitive-types) that are tailored for smart contract use. However, using individual means of storage often becomes cumbersome, so there are two main ways to combine multiple primitives together: [Structs](#structs) and [Messages](#messages).
6+
7+
Note, while Traits and Contracts are also considered a part of the Tacts type system, one can't pass them around like [Structs](#structs) or [Messages](#messages). Instead, one can obtain the initial state of the given Contract by using the [initOf](/book/statements#initof) statement described later in the Book.
8+
9+
<Callout type="warning" emoji="⚠️">
10+
11+
**Warning**: Currently circular types are **not** possible. This means that struct/message **A** can't have a field of a struct/message **B** that has a field of the struct/message **A**.
12+
13+
Therefore, the following code **won't** compile:
14+
15+
```tact
16+
struct A {
17+
circularFieldA: B;
18+
}
19+
20+
struct B {
21+
impossibleFieldB: A;
22+
}
23+
```
24+
25+
</Callout>
26+
27+
## Structs
28+
29+
Structs can define complex data types that contain multiple fields of different types. They can also be nested.
30+
31+
```tact
32+
struct Point {
33+
x: Int as int64;
34+
y: Int as int64;
35+
}
36+
37+
struct Line {
38+
start: Point;
39+
end: Point;
40+
}
41+
```
42+
43+
Structs can also include both default fields and optional fields. This can be quite useful when you have many fields but don't want to keep respecifying them.
44+
45+
```tact
46+
struct Params {
47+
name: String = "Satoshi"; // default value
48+
age: Int?; // optional field
49+
point: Point; // nested Structs
50+
}
51+
```
52+
53+
Structs are also useful as return values from getters or other internal functions. They effectively allow a single getter to return multiple return values.
54+
55+
```tact
56+
contract StructsShowcase {
57+
params: Params; // Struct as a Contract persistent state variable
58+
59+
init() {
60+
self.params = Params{point: Point{x: 4, y: 2}};
61+
}
62+
63+
get fun params(): Params {
64+
return self.params;
65+
}
66+
}
67+
```
68+
69+
The order of fields does not matter. Unlike other languages, Tact does not have any padding between fields.
70+
71+
## Messages
72+
73+
Messages can hold [Structs](#structs) in them:
74+
75+
```tact
76+
struct Point {
77+
x: Int;
78+
y: Int;
79+
}
80+
81+
message Add {
82+
point: Point; // holds a struct Point
83+
}
84+
```
85+
86+
Messages are almost the same thing as [Structs](#structs) with the only difference that Messages have a 32-bit integer header in their serialization containing their unique numeric id. This allows Messages to be used with [receivers](/book/receive) since the contract can tell different types of messages apart based on this id.
87+
88+
Tact automatically generates those unique ids for every received Message, but this can be manually overwritten:
89+
90+
```tact
91+
// This Message overwrites its unique id with 0x7362d09c
92+
message(0x7362d09c) TokenNotification {
93+
forwardPayload: Slice as remaining;
94+
}
95+
```
96+
97+
This is useful for cases where you want to handle certain opcodes (operation codes) of a given smart contract, such as [Jetton standard](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md). The short-list of opcodes this contract is able to process is [given here in FunC](https://github.com/ton-blockchain/token-contract/blob/main/ft/op-codes.fc). They serve as an interface to the smart contract.
98+
99+
<Callout>
100+
101+
For more in-depth information on this see:\
102+
[Convert received messages to `op` operations](/book/func#convert-received-messages-to-op-operations)\
103+
[Internal message body layout in TON Docs](https://docs.ton.org/develop/smart-contracts/guidelines/internal-messages#internal-message-body)\
104+
[Messages of the Jetton implementation in Tact](https://github.com/howardpen9/jetton-implementation-in-tact/blob/9eee917877a92af218002874a9f2bd3f9c619229/sources/messages.tact)\
105+
[Jetton Standard in Tact on Tact-by-Example](https://tact-by-example.org/07-jetton-standard)
106+
107+
</Callout>
108+
109+
## Optionals
110+
111+
As it was mentioned in [type system overview](/book/types), most [primitive types](/book/types#primitive-types), [Structs](#structs) and [Messages](#messages) could be nullable. That is, they don't necessarily hold any value, aside from `null{:tact}` — a special value, which represents the intentional absence of any other value.
112+
113+
[Variables](/book/statements#variable-declaration) or fields of [Structs](#structs) and [Messages](#messages) that can hold `null{:tact}` are called "optionals". They're useful to reduce state size when the variable isn't necesserily used.
114+
115+
You can make any variable an optional by adding a question mark (`?`) after its type declaration. The only exceptions are [`map<>{:tact}`](/book/types#maps) and [`bounced<>{:tact}`](/book/bounced#bounced-messages-in-tact), where you can't make them, inner key/value type (in case of a map) or the inner [Message](#messages) (in case of a bounced) optional.
116+
117+
Optional variables that are not defined hold the `null{:tact}` value by default. You cannot access them without checking for `null{:tact}` first. But if you're certain the optional variable is not `null{:tact}`, use the non-null assertion operator (`!!`, also called double-bang or double exclamation mark operator) to access its value.
118+
119+
Trying to access the value of an optional variable without using `!!` or checking for `null{:tact}` beforehand will result in a compilation error.
120+
121+
Example of optionals:
122+
123+
```tact
124+
struct StOpt {
125+
opt: Int?; // Int or null
126+
}
127+
128+
message MsOpt {
129+
opt: StOpt?; // Notice, how the struct StOpt is used in this definition
130+
}
131+
132+
contract Optionals {
133+
opt: Int?;
134+
address: Address?;
135+
136+
init(opt: Int?) { // optionals as parameters
137+
self.opt = opt;
138+
self.address = null; // explicit null value
139+
}
140+
141+
receive(msg: MsOpt) {
142+
let opt: Int? = 12; // defining a new variable
143+
if (self.opt != null) { // explicit check
144+
self.opt = opt!!; // using !! as we know that opt value isn't null
145+
}
146+
}
147+
}
148+
```

Diff for: pages/book/types.mdx

+42-35
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,52 @@
1-
# Tact type system
1+
# Type system overview
22

3-
Every variable, item, and value in a Tact program has a type:
3+
import { Callout } from 'nextra/components'
44

5-
* Primitives: Int, Bool, Slice, Cell, Builder, String and StringBuilder;
6-
* Map
7-
* Structs and Messages
8-
* Contracts and Traits
5+
Every variable, item, and value in Tact programs has a type. They can be:
96

10-
Also, most primitive types, structs, and messages could be nullable.
7+
* One of the [primitive types](#primitive-types)
8+
* [Maps](#maps)
9+
* Composite types, such as [Structs and Messages](#structs-and-messages)
10+
* or [Contracts](#contracts) and [Traits](#traits)
11+
12+
Also, many of those types [can be made nullable](/book/defining-types#optionals).
1113

1214
## Primitive types
1315

14-
* Int - all integers in Tact are 257-bit signed integers.
15-
* Bool - classical boolean with true/false values.
16-
* Address - standard address.
17-
* Slice, Cell, Builder - low-level primitive of TON VM.
18-
* String - type that represents text strings in TON VM.
19-
* StringBuilder - helper type that allows you to concatenate strings in a gas-efficient way
16+
* Int all numbers in Tact are 257-bit signed integers, but [smaller representations](/book/integers#serialization) can be used to reduce storage costs.
17+
* Bool classical boolean with `true` and `false` values.
18+
* Address standard [smart contract address](https://docs.ton.org/learn/overviews/addresses#address-of-smart-contract) in TON Blockchain.
19+
* Slice, Cell, Builder low-level primitives of TON VM.
20+
* String represents text strings in TON VM.
21+
* StringBuilder helper type that allows you to concatenate strings in a gas-efficient way.
2022

2123
## Structs and Messages
2224

23-
Structs and Messages are almost the same thing with the only difference being that a message has a header in its serialization and therefore could be used as receivers.
24-
25-
> **Warning**
26-
> Currently circular **types** are not possible. This means that struct/message **A** can't have a field of a struct/message **B** that has a field of the struct/message **A**.
25+
[Struct](/book/defining-types#structs) example:
2726

28-
Example:
2927
```tact
3028
struct Point {
3129
x: Int;
3230
y: Int;
3331
}
32+
```
33+
34+
[Message](/book/defining-types#messages) example:
3435

35-
message SetValue {
36+
```tact
37+
// Custom numeric id of the Message
38+
message(0x11111111) SetValue {
3639
key: Int;
3740
value: Int?; // Optional
41+
coins: Int as coins; // Serialization into TL-B types
3842
}
3943
```
4044

45+
Learn more about them on a dedicated page about [defining composite types](/book/defining-types).
46+
4147
## Maps
4248

43-
The type `map<k, v>` is used as a way to associate data with corresponding keys.
49+
The type `map<k, v>{:tact}` is used as a way to associate data with corresponding keys.
4450

4551
Possible key types:
4652
* Int
@@ -51,39 +57,40 @@ Possible value types:
5157
* Bool
5258
* Cell
5359
* Address
54-
* Struct/Message
60+
* [Struct](/book/defining-types#structs)
61+
* [Message](/book/defining-types#messages)
5562

5663
```tact
5764
contract HelloWorld {
58-
counters: map<Int, Int>;
65+
counters: map<Int, Int>;
5966
}
6067
```
6168

6269
## Contracts
6370

64-
Contracts are the main entry of a smart contract on the TON blockchain. It holds all functions, getters, and receivers of a contract.
71+
Contracts are the main entry of a smart contract on the TON blockchain. It holds all functions, getters, and receivers of a TON contract.
6572

6673
```tact
6774
contract HelloWorld {
68-
counter: Int;
75+
counter: Int;
6976
70-
init() {
71-
self.counter = 0;
72-
}
77+
init() {
78+
self.counter = 0;
79+
}
7380
74-
receive("increment") {
75-
self.counter = self.counter + 1;
76-
}
81+
receive("increment") {
82+
self.counter = self.counter + 1;
83+
}
7784
78-
get fun counter(): Int {
79-
return self.counter;
80-
}
85+
get fun counter(): Int {
86+
return self.counter;
87+
}
8188
}
8289
```
8390

8491
## Traits
8592

86-
Tact doesn't support classical class inheritance, but instead introduces the concept of **traits**. Trait defines functions, receivers, and required fields. The trait is like abstract classes, but it does not define how and where fields must be stored. **All** fields from all traits must be explicitly declared in the contract itself. Traits itself also don't have constructors and all initial field initialization also must be done in the main contract.
93+
Tact doesn't support classical class inheritance, but instead introduces the concept of **traits**. Trait defines functions, receivers, and required fields. The trait is like abstract classes, but it does not define how and where fields must be stored. **All** fields from all traits must be explicitly declared in the contract itself. Traits themselves don't have `init(){:tact}` constructors, so all initial field initialization also must be done in the main contract.
8794

8895
```tact
8996
trait Ownable {
@@ -110,4 +117,4 @@ contract Treasure with Ownable {
110117
self.owner = owner;
111118
}
112119
}
113-
```
120+
```

0 commit comments

Comments
 (0)