Containers#

TVM-FFI provides five built-in container types for storing and exchanging collections of values across C++, Python, and Rust. They are all heap-allocated, reference-counted objects that can be stored in Any and passed through the FFI boundary.

The containers split into two categories: immutable containers that use copy-on-write semantics, and mutable containers that use shared-reference semantics.

Overview#

Type

C++ Class

Python Class

Mutability

Semantics

Array

Array<T>

tvm_ffi.Array

Immutable

Homogeneous sequence with copy-on-write

List

List<T>

tvm_ffi.List

Mutable

Homogeneous sequence with shared-reference

Tuple

Tuple<Ts...>

(backed by tvm_ffi.Array)

Immutable

Heterogeneous fixed-size sequence (backed by ArrayObj)

Map

Map<K, V>

tvm_ffi.Map

Immutable

Homogeneous key-value mapping with copy-on-write

Dict

Dict<K, V>

tvm_ffi.Dict

Mutable

Homogeneous key-value mapping with shared-reference

Immutable Containers (Copy-on-Write)#

Array#

Array<T> is an immutable homogeneous sequence backed by ArrayObj. It implements copy-on-write semantics: when a mutation method is called in C++ (e.g. push_back, Set), the array checks whether the backing storage is uniquely owned. If it is shared with other handles, it copies the data first so that existing handles are unaffected.

ffi::Array<int> a = {1, 2, 3};
ffi::Array<int> b = a;       // b shares the same ArrayObj
a.push_back(4);              // copy-on-write: a gets a new backing storage
assert(a.size() == 4);
assert(b.size() == 3);       // b is unchanged

In Python, tvm_ffi.Array implements collections.abc.Sequence (read-only). When a Python list or tuple is passed to an FFI function, it is automatically converted to Array.

Tuple#

Tuple<T1, T2, ...> is an immutable heterogeneous fixed-size sequence. It is backed by the same ArrayObj as Array, but provides compile-time type safety for each element position via C++ variadic templates.

ffi::Tuple<int, ffi::String, bool> t(42, "hello", true);
int x = t.get<0>();              // 42
ffi::String s = t.get<1>();      // "hello"

In Python, Tuple does not have a separate class – Python tuples passed through the FFI are converted to Array.

Map#

Map<K, V> is an immutable homogeneous key-value mapping backed by MapObj. It implements copy-on-write semantics (same principle as Array). Insertion order is preserved.

ffi::Map<ffi::String, int> m = {{"Alice", 100}, {"Bob", 95}};
ffi::Map<ffi::String, int> m2 = m;  // m2 shares the same MapObj
m.Set("Charlie", 88);               // copy-on-write
assert(m.size() == 3);
assert(m2.size() == 2);             // m2 is unchanged

In Python, tvm_ffi.Map implements collections.abc.Mapping (read-only). When a Python dict is passed to an FFI function, it is automatically converted to Map.

Mutable Containers (Shared Reference)#

List#

List<T> is a mutable homogeneous sequence backed by ListObj. Unlike Array, it does not use copy-on-write. Mutations happen directly on the underlying shared object, and all handles sharing the same ListObj see the mutations immediately.

ffi::List<int> a = {1, 2, 3};
ffi::List<int> b = a;       // b shares the same ListObj
a.push_back(4);             // in-place mutation
assert(a.size() == 4);
assert(b.size() == 4);      // b sees the mutation

In Python, tvm_ffi.List implements collections.abc.MutableSequence and supports append, insert, __setitem__, __delitem__, pop, reverse, clear, and extend.

Dict#

Dict<K, V> is a mutable homogeneous key-value mapping backed by DictObj. Like List, mutations happen directly on the shared object with no copy-on-write.

ffi::Dict<ffi::String, int> d = {{"Alice", 100}};
ffi::Dict<ffi::String, int> d2 = d;  // d2 shares the same DictObj
d.Set("Bob", 95);                    // in-place mutation
assert(d.size() == 2);
assert(d2.size() == 2);              // d2 sees the mutation

In Python, tvm_ffi.Dict implements collections.abc.MutableMapping and supports __setitem__, __delitem__, pop, clear, and update.

When to Use Each Type#

Use Case

Container

Immutable snapshot of a sequence (e.g. function arguments)

Array

Building up or modifying a sequence in-place

List

Fixed heterogeneous collection (e.g. a multi-typed return value)

Tuple

Immutable key-value lookup (e.g. configuration)

Map

Mutable key-value store (e.g. accumulating results)

Dict

Thread Safety#

Immutable containers (Array, Tuple, Map) can be safely shared across threads for read-only access. Copy-on-write mutations are thread-safe because they create a new backing object when the storage is shared.

Mutable containers (List, Dict) are NOT thread-safe. If multiple threads need to read or write the same List or Dict, external synchronization (e.g. a mutex) is required.

Further Reading#