Строки и работа с UTF-8
Понимаем, что строка в Zig — это просто срез байтов, и как с ними жить.
Строка в Zig — это срез байтов
[]const u8; язык не навязывает кодировку, но строковые литералы хранятся в UTF-8, поэтому длина в байтах может не совпадать с числом символов.
В Zig нет отдельного строкового типа. Строка — это []const u8, то есть срез неизменяемых байтов. Это честно отражает реальность: в памяти строка и есть последовательность байтов. Такой подход прост и предсказуем, но требует понимать разницу между байтами и символами.
Литералы — это байты в UTF-8
const std = @import("std");
pub fn main() void {
const s = "Привет"; // тип: *const [12:0]u8 — 12 БАЙТ, не 6 символов
std.debug.print("байт в строке: {d}\n", .{s.len});
}
Вывод:
байт в строке: 12
Слово «Привет» — это 6 кириллических букв, но в UTF-8 каждая занимает по 2 байта, итого 12. s.len возвращает длину в байтах, а не в символах. Это ключевой момент: для ASCII байт = символ, но для кириллицы, эмодзи и прочего — нет.
Сравнение и поиск строк
const std = @import("std");
pub fn main() void {
const a = "zig";
const eq = std.mem.eql(u8, a, "zig"); // сравнение байтов
const has = std.mem.indexOf(u8, "hello zig", "zig"); // позиция или null
std.debug.print("eq={} pos={?d}\n", .{ eq, has });
}
Вывод:
eq=true pos=6
Поскольку строка — срез байтов, сравнивают их через std.mem.eql(u8, ...) (оператор == для срезов не работает по содержимому). indexOf ищет подстроку и возвращает опциональную позицию. Формат {?d} печатает опциональное число, разворачивая его.
Подсчёт реальных символов
Чтобы посчитать символы (а не байты) в UTF-8, нужен декодер. Стандартная библиотека предоставляет std.unicode.Utf8View для итерации по кодовым точкам. Это важно: наивный проход по байтам разрежет многобайтовый символ.
// итерация по кодовым точкам, а не по байтам
var it = (try std.unicode.Utf8View.init("Привет")).iterator();
var count: usize = 0;
while (it.nextCodepoint()) |_| count += 1;
// count == 6 (символов), хотя байт было 12
Как работает под капотом
UTF-8 кодирует символы переменным числом байтов: ASCII — 1 байт, кириллица — 2, многие иероглифы — 3, эмодзи — 4. Поэтому индексация s[i] даёт байт, а не символ, и «разрезать» строку по произвольному байту опасно — можно попасть в середину символа. Декодер UTF-8 читает байты группами по правилам кодировки и выдаёт корректные кодовые точки.
Частые ошибки
Первая — считать s.len числом символов: это число байтов. Вторая — сравнивать строки через ==: для срезов это сравнение указателей, а не содержимого; нужен std.mem.eql. Третья — резать UTF-8 строку по произвольному индексу и портить многобайтовый символ.
Итог
- Строка в Zig — это
[]const u8, срез байтов; отдельного строкового типа нет. - Литералы хранятся в UTF-8;
s.len— длина в байтах, не в символах. - Сравнивают строки через
std.mem.eql, ищут черезstd.mem.indexOf. - Для подсчёта символов нужен UTF-8-декодер (
std.unicode.Utf8View).