Book
Type system overview

Type system overview

Every variable, item, and value in Tact programs has a type. They can be:

Also, many of those types can be made nullable.

Primitive types

  • Int — all numbers in Tact are 257-bit signed integers, but smaller representations can be used to reduce storage costs.
  • Bool — classical boolean with true and false values.
  • Address — standard smart contract address (opens in a new tab) in TON Blockchain.
  • Slice, Cell, Builder — low-level primitives of TON VM.
  • String — represents text strings in TON VM.
  • StringBuilder — helper type that allows you to concatenate strings in a gas-efficient way.

Structs and Messages

Struct example:

struct Point {
    x: Int;
    y: Int;
}

Message example:

// Custom numeric id of the Message
message(0x11111111) SetValue {
    key: Int;
    value: Int?; // Optional
    coins: Int as coins; // Serialization into TL-B types
}

Learn more about them on a dedicated page about defining composite types.

Maps

The type map<k, v> is used as a way to associate data with corresponding keys.

Possible key types:

  • Int
  • Address

Possible value types:

contract HelloWorld {
    counters: map<Int, Int>;
}

Contracts

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

contract HelloWorld {
    counter: Int;
 
    init() {
        self.counter = 0;
    }
 
    receive("increment") {
        self.counter = self.counter + 1;
    }
 
    get fun counter(): Int {
        return self.counter;
    }
}

Traits

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() constructors, so all initial field initialization also must be done in the main contract.

trait Ownable {
    owner: Address;
 
    fun requireOwner() {
        nativeThrowUnless(132, context().sender == self.owner);
    }
 
    get fun owner(): Address {
        return self.owner;
    }
}

And the contract that uses trait:

contract Treasure with Ownable {
  owner: Address; // Field from trait MUST be defined in contract itself
 
  // Here we init the way we need, trait can't specify how you must init owner field
  init(owner: Address) {
    self.owner = owner;
  }
}