errdefer и безопасный откат
Осваиваем errdefer — откат ресурсов именно тогда, когда что-то пошло не так.
errdefer — отложенное выражение, которое выполняется при выходе из блока только из-за ошибки; идеально для отката частично выполненной инициализации без утечек.
Представьте функцию, которая выделяет три ресурса по очереди. Если третий выделить не удалось, первые два нужно освободить, иначе утечка. В C это превращается в лесенку goto cleanup. Zig решает задачу элегантно через errdefer.
defer против errdefer
Мы уже знаем defer: он выполняется при любом выходе из блока. errdefer — его условный вариант: он срабатывает только если блок завершается из-за возвращённой ошибки. Если функция завершилась успешно — errdefer не выполняется.
fn createUser(alloc: Allocator) !*User {
const user = try alloc.create(User);
errdefer alloc.destroy(user); // освободим, только если дальше ошибка
user.name = try alloc.dupe(u8, "Аня");
errdefer alloc.free(user.name);
user.id = try generateId(); // если здесь ошибка —
// сработают оба errdefer в обратном порядке
return user; // успех: ни один errdefer не выполнится
}
Логика читается линейно. После каждого успешного выделения сразу ставится errdefer на его освобождение. Если последующий шаг падает с ошибкой, все накопленные errdefer выполняются в обратном порядке — ресурсы аккуратно откатываются. При успехе же управление доходит до return, и ни один errdefer не срабатывает: ресурсы остаются у вызывающего.
Порядок выполнения
И defer, и errdefer выполняются в порядке, обратном объявлению (как стек). Это естественно: последний выделенный ресурс освобождается первым, повторяя последовательность захвата в обратную сторону.
Как работает под капотом
Компилятор отслеживает, по какому пути управление покидает блок. Для errdefer он вставляет код освобождения только в ветки, где возвращается ошибка. На успешном пути этого кода нет вовсе — нулевые накладные расходы. Это и есть явное, предсказуемое управление ресурсами: вы видите каждое освобождение в коде, а компилятор гарантирует, что оно сработает на нужных путях.
Частые ошибки
Первая — поставить defer вместо errdefer там, где ресурс должен пережить успешный возврат: тогда вы освободите то, что вернули, и получите висячий указатель. Вторая — забыть errdefer после успешного выделения и получить утечку при ошибке на следующем шаге. Третья — ставить errdefer до самого выделения: откатывать ещё нечего.
Итог
errdeferвыполняется только при выходе из блока из-за ошибки.- Идиома: после каждого успешного выделения сразу ставить
errdeferна откат. - При успехе
errdeferне срабатывает — ресурс достаётся вызывающему. - Выполняются в обратном порядке объявления, как стек; на успешном пути — нулевые расходы.