Массивы и многоуказатели
Углубляемся в массивы фиксированного размера и завершённые указатели для дружбы с C.
Sentinel-завершённый указатель
[*:0]T— указатель, после последнего полезного элемента которого стоит маркер-ограничитель (для строк это0); так Zig представляет C-строки.
Массив в Zig — это последовательность фиксированной, известной на этапе компиляции длины. Его тип записывается как [N]T, и длина — часть типа. Это отличает массив от среза, у которого длина известна только в рантайме.
Массив фиксированного размера
const std = @import("std");
pub fn main() void {
const a = [_]i32{ 1, 2, 3, 4 }; // [_] — длина выводится: [4]i32
var sum: i32 = 0;
for (a) |x| sum += x;
std.debug.print("сумма={d} длина={d}\n", .{ sum, a.len });
}
Вывод:
сумма=10 длина=4
Синтаксис [_]i32 просит компилятор вывести длину из числа элементов. Можно указать длину явно: [4]i32. Поскольку длина — часть типа, массивы [3]i32 и [4]i32 — разные типы, и перепутать их компилятор не даст.
Массив на стеке против среза
Массив [N]T хранит данные прямо в себе (на стеке или внутри структуры). Срез []T только ссылается на чужие данные. Поэтому функции обычно принимают срез: он работает с массивом любой длины, не копируя его.
fn sumSlice(items: []const i32) i32 {
var s: i32 = 0;
for (items) |x| s += x;
return s;
}
// вызов: sumSlice(&a) — массив автоматически приводится к срезу
Sentinel-завершённые типы для C
C-строки завершаются нулевым байтом. Zig выражает это через sentinel: [*:0]const u8 — указатель на байты, после которых стоит 0. Строковые литералы Zig на самом деле имеют тип *const [N:0]u8 — массив с нулём-ограничителем, что позволяет передавать их в C-функции напрямую.
const name: [*:0]const u8 = "Zig"; // готово к передаче в C
// в C это был бы просто const char*
Как работает под капотом
Sentinel — это гарантия компилятора, что в памяти после данных лежит маркер. Для строк это позволяет C-функциям вроде strlen найти конец, считая байты до нуля. При этом Zig-срез строки знает длину и так, поэтому внутри Zig работа со строками не зависит от завершающего нуля — он нужен лишь на границе с C.
Частые ошибки
Первая ошибка — считать массив и срез одним и тем же: у массива длина в типе, у среза в рантайме. Вторая — забыть, что строковый литерал sentinel-завершён, и удивляться его типу. Третья — пытаться индексировать массив за его границы: в safe-сборке это паника, как и со срезом.
Итог
- Массив
[N]Tхранит данные в себе; длина — часть типа. [_]T{...}просит вывести длину из числа элементов.- Функции обычно принимают срез
[]const T— массив приводится к нему автоматически. [*:0]const u8— sentinel-завершённый указатель, представление C-строк.