Type system overview
Every variable, item, and value in Tact programs has a type. They can be:
- One of the primitive types
- Maps
- Composite types, such as Structs and Messages
- or Contracts and Traits
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
andfalse
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;
}
}