errdefer и безопасный откат
Ты готовишь заказ из двух предметов. Первый уже снят со склада, но второго не оказалось. На успешном пути первый уходит клиенту. На неуспешном его нужно вернуть, иначе склад постепенно опустеет. Простое «вернуть всегда» тоже неверно: тогда клиент не получит оплаченный предмет.
Так возникает два вида уборки: действие при любом выходе и откат только при ошибке. Zig позволяет записать оба рядом с приобретением ресурса.
Одна мысль урока
errdefer выполняет откат при выходе с ошибкой, не отбирая ресурс у успешного результата.
Опора из прошлого урока: catch и обработка на месте.
От картинки к записи
defer запоминает действие на выход. Оно не выполняется сразу в строке объявления. Несколько действий выполняются в обратном порядке: последнее поставленное первым убирается. Это похоже на разбор стопки коробок, которую собирали снизу вверх.
errdefer
нужен для отката частично выполненной работы. Если функция возвращает успешный результат, действие
не выполняется. Именно поэтому можно выделить память, поставить errdefer free и вернуть эту память
вызывающему без её преждевременного освобождения.
В примере познакомимся с выделением минимально. allocator.alloc(u8, count) просит count байтов и может вернуть ошибку нехватки памяти. allocator.free(bytes) возвращает полученный блок. Аллокатор подробно разберём в уроке 16. Сейчас важно только поставить уборку после успешного получения блока, но до следующего действия с возможной ошибкой.
Функция copyChecked копирует непустую строку. Сначала выделяет блок, затем регистрирует откат. После этого проверяет пустой вход. Пустой пример намеренно даёт ошибку после приобретения ресурса, чтобы путь отката был виден. В прикладном коде дешёвую проверку обычно выгоднее сделать до выделения; здесь порядок служит разбору жизненного цикла.
Для успешного входа @memcpy заполняет весь результат и функция его возвращает. Теперь освобождение обязан сделать вызывающий. Для пустого входа возвращается error.Empty, и errdefer убирает выделение. Если поставить обычный defer вместо errdefer, память будет возвращена аллокатору даже при успехе; возвращаемый срез станет недействительным.
Владение
здесь задаётся договором функций, а не отдельной автоматической системой. До успешного возврата
блоком владеет copyChecked. После него владелец вызывающий. Ошибка означает, что передача не
состоялась, поэтому исходный владелец обязан откатить приобретение.
Область действия имеет значение. errdefer, поставленный во внутреннем блоке, срабатывает при ошибочном выходе именно из этого блока. Он не хранится до конца всей программы. Не выноси регистрацию далеко от строки выделения: читателю сложнее понять, какой ресурс уже получен и что защищает конкретное действие.
премиум-урок
Продолжение в премиуме: 1 200 ₽ в месяц или 10 000 ₽ в год. Вместе с ним серверные проверки задач и школьные токены AI-коуча.
Оплата пока не подключена: премиум и курсы открываются промокодом.