Book
Structs and Messages

Structs and Messages

Tact supports a number of primitive data types that are tailored for smart contract use. However, using individual means of storage often becomes cumbersome, so there are Structs and Messages which allow combining types together.

⚠️

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:

struct A {
    circularFieldA: B;
}
 
struct B {
    impossibleFieldB: A;
}

Structs

Structs can define complex data types that contain multiple fields of different types. They can also be nested.

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.

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.

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 in them:

struct Point {
    x: Int;
    y: Int;
}
 
message Add {
    point: Point; // holds a struct Point
}

Messages are almost the same thing as 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 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:

// 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 (opens in a new tab). The short-list of opcodes this contract is able to process is given here in FunC (opens in a new tab). They serve as an interface to the smart contract.