Type system overview
Every variable, item, and value in Tact programs has a type. They can be:
- One of the primitive types
- Or one of the composite types
Additionally, many of these 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 -bit signed integers, but smaller representations can be used to reduce storage costs.Bool
— Classical boolean withtrue
andfalse
values.Address
— Standard smart contract address in TON Blockchain.Cell
,Builder
,Slice
— Low-level primitives of TVM.String
— Immutable text strings.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 two values: true
and false
. It is 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 is not possible. However, many comparison operators are available, such as:
&&
for logical AND with its augmented assignment version&&=
,||
for logical OR with its augmented assignment version||=
,!
for logical inversion,==
and!=
for checking equality,- and
!!
for non-null assertion.
Persisting bools to state is very space-efficient, as they only occupy 1 bit. Storing 1000 bools in state costs about 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:
- Maps — associations of keys with values.
- Structs and Messages — data structures with typed fields.
- Optionals —
null
values for variables or fields of Structs and Messages.
In addition to the composite types above, Tact provides a special type constructor bounced<T>
, which can only be specified in bounced message receivers.
While contracts and traits are also considered a part of the Tact type system, one cannot pass them around like Structs and Messages. Instead, it is possible to obtain the initial state of a given contract by using the initOf
expression.
It is also possible to obtain only the code of a given contract by using the codeOf
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 the dedicated page: Maps.
Structs and Messages
Structs and Messages are the 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 Messagemessage(0x11111111) SetValue { key: Int; value: Int?; // Optional, Int or null coins: Int as coins; // Serialization into TL-B types}
Learn more about them on the dedicated page: Structs and Messages.
Optionals
All primitive types, as well as Structs and Messages, can be nullable and hold a special null
value.
Example of an optional:
let opt: Int? = null; // Int or null, explicitly assigned null
Learn more about them on the dedicated page: Optionals.
Contracts
Contracts in Tact conveniently represent smart contracts on 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 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.
A trait can also allow the contract inheriting it to override the behavior of its functions and the values of its constants.
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() { throwUnless(132, context().sender == self.owner); }
// Getter function with return type Address get fun owner(): Address { return self.owner; }}
And the contract that uses the 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 on-chain init(owner: Address) { self.owner = owner; }}
Alternatively, a contract may use the contract parameter syntax, in which case it must list all the persistent state variables inherited from all of its traits:
contract Treasure( // Persistent state variable, to be defined at deployment owner: Address,) with Ownable {}