Type system

Tact type system

Every variable, item, and value in a Tact program has a type:

  • Primitives: Int, Bool, Slice, Cell, Builder, String and StringBuilder;
  • Map
  • Structs and Messages
  • Contracts and Traits

Also most primitive types, structs and messages could be nullable.

Primitive types

  • Int - all integers in Tact are 257 bit signed integers.
  • Bool - classical boolean with true/false values.
  • Address - standart address.
  • Slice, Cell, Builder - low level primitive of TON VM.
  • String - type that represents text strings in TON VM.
  • StringBuilder - helper type that allows you to concatenate strings in gas-efficient way

Structs and Messages

Structs and Messages are almost the same thing with an only difference that message has a header in it's serialization and therefore could be used as receivers.

Warning Currently circular types are not possible. Meaning that struct/message A can't have field of a struct/message B that have field of a struct/message A.


struct Point {
    x: Int;
    y: Int;
message SetValue {
    key: Int;
    value: Int?; // Optional


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

Possible key types:

  • Int
  • Address

Possible value types:

  • Int
  • Bool
  • Cell
  • Address
  • Struct/Message
contract HelloWorld {
  counters: map<Int, Int>;


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

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


Tact doesn't support classical class inheritance, but instead introduces concept of traits. Trait defines functions, receivers and required fields. 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 main contract.

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

And 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;