Book
Type system overview

Type system overview

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

Additionally, many of those types can be made nullable.

Primitive types

Tact supports a number of primitive data types that are tailored for smart contract use:

  • Int — all numbers in Tact are 257257-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.

Booleans

The primitive type Bool is the classical boolean type, which can hold only the two values: true and false. It's convenient for boolean and logical operations, as well as for storing flags.

There are no implicit type conversions in Tact, so addition (+) of two boolean values isn't possible. Hovewer, many comparison operators are available, such as:

Persisting bools to state is very space-efficient, as they only take 1-bit. Storing 1000 bools in state costs (opens in a new tab) about 0.000720.00072 TON per year.

Composite types

Using individual means of storage often becomes cumbersome, so there are ways to combine multiple primitive types together to create composite types:

Note, while contracts and traits are also considered a part of the Tacts type system, one can't pass them around like Structs and Messages. Instead, it's possible to obtain the initial state of the given contract by using the initOf expression.

Maps

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

Example of a map<k, v>:

let mapExample: map<Int, Int> = emptyMap(); // empty map with Int keys and values

Learn more about them on a dedicated page: Maps.

Structs and Messages

Structs and Messages are two main ways of combining multiple primitive types into a composite one.

Example of a Struct:

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

Example of a Message:

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

Learn more about them on a dedicated page: Structs and Messages.

Optionals

All primitive types, as well as Structs and Messages could be nullable and hold a special null value.

Example of an optional:

let opt: Int? = null; // Int or null, with explicitly assigned null

Learn more about them on a dedicated page: Optionals.

Contracts

Contracts in Tact serve as the main entrypoints of smart contracts of TON blockchain. They hold all functions, getters, and receivers of a TON contract, and much more.

Example of a contract:

contract HelloWorld {
    // Persistent state variable
    counter: Int;
 
    // Constructor function init(), where all the variables are initialized
    // Note, that an empty init function won't be needed starting with Tact 1.3.0,
    // see: https://github.com/tact-lang/tact/pull/167
    init() {
        self.counter = 0;
    }
 
    // Internal message receiver, which responds to a string message "increment"
    receive("increment") {
        self.counter += 1;
    }
 
    // Getter function with return type Int
    get fun counter(): Int {
        return self.counter;
    }
}

Read more about them on the dedicated page: Contracts.

Traits

Tact doesn't support classical class inheritance, but instead introduces the concept of traits, which can be viewed as abstract contracts (like abstract classes in popular object-oriented languages). They have the same structure as contracts, but can't initialize persistent state variables, while allowing to override some of their behaviors.

Example of a trait Ownable from @stdlib/ownable:

trait Ownable {
    // Persistent state variable, which cannot be initialized in the trait
    owner: Address;
 
    // Internal function
    fun requireOwner() {
        nativeThrowUnless(132, context().sender == self.owner);
    }
 
    // Getter function with return type Address
    get fun owner(): Address {
        return self.owner;
    }
}

And the contract that uses trait Ownable:

contract Treasure with Ownable {
    // persistent state variable, which MUST be defined in the contract
    owner: Address;
 
    // constructor function init(), where all the variables are initialized
    init(owner: Address) {
        self.owner = owner;
    }
}