forked from tact-lang/tact-docs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Struct & Message declaration syntax + Optionals (tact-lang#95)
* added motivation to use custom numeric ids to match opcodes
- Loading branch information
Showing
3 changed files
with
193 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
# Defining composite types | ||
|
||
import { Callout } from 'nextra/components' | ||
|
||
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). | ||
|
||
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. | ||
|
||
<Callout type="warning" emoji="⚠️"> | ||
|
||
**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**. | ||
|
||
Therefore, the following code **won't** compile: | ||
|
||
```tact | ||
struct A { | ||
circularFieldA: B; | ||
} | ||
struct B { | ||
impossibleFieldB: A; | ||
} | ||
``` | ||
|
||
</Callout> | ||
|
||
## Structs | ||
|
||
Structs can define complex data types that contain multiple fields of different types. They can also be nested. | ||
|
||
```tact | ||
struct Point { | ||
x: Int as int64; | ||
y: Int as int64; | ||
} | ||
struct Line { | ||
start: Point; | ||
end: Point; | ||
} | ||
``` | ||
|
||
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. | ||
|
||
```tact | ||
struct Params { | ||
name: String = "Satoshi"; // default value | ||
age: Int?; // optional field | ||
point: Point; // nested Structs | ||
} | ||
``` | ||
|
||
Structs are also useful as return values from getters or other internal functions. They effectively allow a single getter to return multiple return values. | ||
|
||
```tact | ||
contract StructsShowcase { | ||
params: Params; // Struct as a Contract persistent state variable | ||
init() { | ||
self.params = Params{point: Point{x: 4, y: 2}}; | ||
} | ||
get fun params(): Params { | ||
return self.params; | ||
} | ||
} | ||
``` | ||
|
||
The order of fields does not matter. Unlike other languages, Tact does not have any padding between fields. | ||
|
||
## Messages | ||
|
||
Messages can hold [Structs](#structs) in them: | ||
|
||
```tact | ||
struct Point { | ||
x: Int; | ||
y: Int; | ||
} | ||
message Add { | ||
point: Point; // holds a struct Point | ||
} | ||
``` | ||
|
||
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. | ||
|
||
Tact automatically generates those unique ids for every received Message, but this can be manually overwritten: | ||
|
||
```tact | ||
// This Message overwrites its unique id with 0x7362d09c | ||
message(0x7362d09c) TokenNotification { | ||
forwardPayload: Slice as remaining; | ||
} | ||
``` | ||
|
||
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. | ||
|
||
<Callout> | ||
|
||
For more in-depth information on this see:\ | ||
[Convert received messages to `op` operations](/book/func#convert-received-messages-to-op-operations)\ | ||
[Internal message body layout in TON Docs](https://docs.ton.org/develop/smart-contracts/guidelines/internal-messages#internal-message-body)\ | ||
[Messages of the Jetton implementation in Tact](https://github.com/howardpen9/jetton-implementation-in-tact/blob/9eee917877a92af218002874a9f2bd3f9c619229/sources/messages.tact)\ | ||
[Jetton Standard in Tact on Tact-by-Example](https://tact-by-example.org/07-jetton-standard) | ||
|
||
</Callout> | ||
|
||
## Optionals | ||
|
||
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. | ||
|
||
[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. | ||
|
||
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. | ||
|
||
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. | ||
|
||
Trying to access the value of an optional variable without using `!!` or checking for `null{:tact}` beforehand will result in a compilation error. | ||
|
||
Example of optionals: | ||
|
||
```tact | ||
struct StOpt { | ||
opt: Int?; // Int or null | ||
} | ||
message MsOpt { | ||
opt: StOpt?; // Notice, how the struct StOpt is used in this definition | ||
} | ||
contract Optionals { | ||
opt: Int?; | ||
address: Address?; | ||
init(opt: Int?) { // optionals as parameters | ||
self.opt = opt; | ||
self.address = null; // explicit null value | ||
} | ||
receive(msg: MsOpt) { | ||
let opt: Int? = 12; // defining a new variable | ||
if (self.opt != null) { // explicit check | ||
self.opt = opt!!; // using !! as we know that opt value isn't null | ||
} | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters