Коллекции: ArrayList и HashMap
Работаем с динамическим массивом и словарём из стандартной библиотеки.
ArrayList — динамический массив из стандартной библиотеки Zig, который растёт по мере добавления элементов; как и всё, что трогает кучу, он требует явного аллокатора.
Раз строки и срезы фиксированы по длине, для растущих данных нужны динамические коллекции. std.ArrayList — это динамический массив (аналог vector в C++), а std.HashMap — словарь ключ-значение. Обе — generic-типы, порождённые через comptime, и обе требуют аллокатор.
ArrayList — динамический массив
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const alloc = gpa.allocator();
var list = std.ArrayList(i32).init(alloc); // тип элемента — i32
defer list.deinit(); // освободить память списка
try list.append(10);
try list.append(20);
try list.append(30);
std.debug.print("len={d} sum={d}\n", .{ list.items.len, list.items[0] + list.items[2] });
}
Вывод:
len=3 sum=40
std.ArrayList(i32) — это generic-тип для i32, созданный comptime-функцией. Его инициализируют аллокатором (init(alloc)), а после работы освобождают через deinit() — отсюда обязательный defer list.deinit(). Метод append добавляет элемент и может вернуть ошибку (память кончилась), поэтому try. Доступ к данным — через поле list.items (это срез).
HashMap — словарь
var map = std.StringHashMap(i32).init(alloc); // ключ — строка, значение — i32
defer map.deinit();
try map.put("яблоки", 5);
try map.put("груши", 3);
if (map.get("яблоки")) |count| {
// count == 5
}
StringHashMap(i32) — словарь со строковыми ключами и значениями i32. put вставляет пару, get возвращает опциональное значение (ключа может не быть — отсюда ? и проверка через if-захват). Это снова безопасность через опциональные типы: отсутствие ключа невозможно проигнорировать.
Как работает под капотом
ArrayList хранит срез-буфер и текущую длину. При append, если буфер полон, он запрашивает у аллокатора буфер вдвое больше, копирует данные и освобождает старый — отсюда амортизированная константная стоимость добавления. Поскольку аллокатор явный, рост виден и контролируем. HashMap внутри держит таблицу с открытой адресацией; и тут все выделения идут через переданный аллокатор.
Частые ошибки
Первая — забыть defer list.deinit() и получить утечку (GPA об этом сообщит). Вторая — обращаться к list напрямую вместо list.items: элементы лежат в поле items. Третья — игнорировать опциональность get: результат нужно распаковать через if или orelse, ключа может не быть.
Итог
std.ArrayList(T)— динамический массив; инициализируют аллокатором, освобождаютdeinit().appendможет вернуть ошибку (память) — отсюдаtry; данные в полеitems.StringHashMap(V)— словарь;getвозвращает опциональное значение.- Рост буфера и все выделения идут через явный аллокатор — поведение контролируемо.