Skip to main content
In TVM, a tuple in the form [T1, T2, ..., T255] is a dynamic container that stores from 0 to 255 elements in a single stack slot. Tuples cannot be serialized to cells and are only present on the stack during the compute phase. Yet they may be returned from get methods because contract getters operate directly on the stack. Tolk supports two tuple types:
  • tuple — an opaque dynamic tuple with unknown shape;
  • [T1, T2, ...] — a typed tuple with a known shape.
In many general-purpose languages, syntax [A, B, C, ...] is used for array or list literals, while syntax (A, B, C, ...) is used for tuples. However, in TON, there are no arrays or lists. Tuple literals use the [A, B, C, ...] syntax, while (A, B, C, ...) is used by tensors: a distinct type that represents ordered collections of values that occupy multiple TVM stack entries.For example, a tuple [int, int, int] is a single stack entry containing three integers. In contrast, a tensor (int, int, int) signifies three separate integers that occupy individual stack entries or are serialized sequentially.

Dynamic tuples

A tuple stores from 0 to 255 elements in one stack slot:
var t: tuple = createEmptyTuple();
t.push(10);
t.push(beginCell());
t.push(null);

t.size(); // 3

Typed tuples

Use [T1, T2, ...] to describe a tuple with a known shape, with non-primitive values not permitted:
// The type is `[int, int, builder]`
val t = [1, 2, beginCell()];

// Read
t.0; // 1

// Write
t.1 = 123;
t.2.storeInt(t.0, 16);

// t is now [1, 123, builder "0x0001"]
t.100500 // compilation error!
A typed tuple may be destructured into multiple variables:
fun sumFirstTwo(t: [int, int, builder]) {
    val [first, second, _] = t;
    return first + second;
}

Component access

The get() method accesses a value by an index. When calling it on opaque dynamic tuples, explicitly provide a type of the retrieved value or rely on the compiler’s type inference:
// Explicit int type
val first = t.get<int>(0);

// Type inference based on desired variable type
val first: int = t.get(0);
The syntax tuple.{i} is permitted when the type is evident:
val first: int = t.0;
The set() method writes a new value at an index. It does not create new elements — instead, it overrides existing ones.
val t = [1, 7];

// t is now [42, 7]
t.set(0, 42);
If index is out of bounds in get() or set(), an exception is thrown:
t.set(value, 100500);    // throws errCode=5
t.100500 = value;        // throws errCode=5
Accessing a tuple value takes a single TVM instruction, whereas accessing a tensor value requires stack shuffling.
Additional methods such as last(), pop(), and others are available in the standard library.

Allowed values

Consider the following structure:
struct Point3d {
    x: int
    y: int
    z: int
}
An attempt to call t.push(somePoint) where somePoint is an instance of that structure raises an error — values that take more than one stack slot cannot be placed in a tuple. That is, only primitive, atomic types can be set:
t.push(somePoint.x);  // ok (int)
t.push(somePoint);    // error

Conversion to and from composites

For composite types, there are generic built-in methods T.toTuple() and T.fromTuple() which convert composites to and from tuples. Length of the resulting tuple is equal to the number of stack slots occupies by a value of the converted type: if it occupies N stack slots, the resulting tuple has size N.
struct Point3d {
    x: int
    y: int
    z: int
}

fun demo(p: Point3d) {
    val t = p.toTuple();       // a tuple with 3 elements
    t.get<int>(2);             // z
    p = Point3d.fromTuple(t);  // back to a struct
}

Lisp-style lists

Lisp-style lists in Tolk are nested two-element tuples. For instance, [1, [2, [3, null]]] represents the list [1, 2, 3]. An empty list is conventionally represented as null. From the type system perspective, a Lisp-style list is a tuple? with dynamic contents. Process such lists using the following standard library module:
import "@stdlib/lisp-lists"
Lisp-style lists allow storing and manipulating more than 255 elements, but they do not have fast element access. If there is a clear upper bound on the number of elements, use maps instead. Otherwise, prefer contract sharding.