Обработка ошибок: pcall и error

Учимся ловить ошибки так, чтобы программа не падала целиком.

pcall (protected call) — функция, которая вызывает другую функцию «под защитой»: если внутри случится ошибка, программа не упадёт, а сообщит о проблеме.

Ошибки неизбежны: пользователь ввёл текст вместо числа, файл не нашёлся. Без защиты любая ошибка останавливает всю программу. В игре это означает вылет — недопустимо.

Генерация ошибки: error

Функция error намеренно вызывает ошибку с сообщением:

local function divide(a, b)
  if b == 0 then
    error("деление на ноль!")
  end
  return a / b
end

Если вызвать divide(10, 0) напрямую — программа аварийно остановится с этим сообщением.

Защита через pcall

pcall запускает функцию безопасно и возвращает два значения: успех (true/false) и результат либо текст ошибки:

local function divide(a, b)
  if b == 0 then error("деление на ноль!") end
  return a / b
end

local ok, result = pcall(divide, 10, 2)
print(ok, result)

local ok2, err = pcall(divide, 10, 0)
print(ok2, err)

Вывод:

true	5
false	деление на ноль!

При успехе ok равен true и второе значение — результат. При ошибке ok равен false и второе значение — сообщение об ошибке. Программа продолжает работать!

Типичный шаблон

local ok, result = pcall(riskyFunction)
if ok then
  print("Успех:", result)
else
  print("Поймана ошибка:", result)
end

Схема защищённого вызова

pcall(f)
 ├─ f отработала         ──> true,  результат
 └─ f бросила error()    ──> false, текст ошибки
        (программа НЕ падает)

Как работает под капотом

Внутри pcall Lua устанавливает «точку восстановления» перед вызовом функции. Если случается error, исполнение «отматывается» назад к этой точке вместо аварийной остановки. Есть и расширенная версия xpcall, которая дополнительно принимает обработчик для красивого сообщения со стеком вызовов.

Частые ошибки

  • Вызывать функцию как pcall(divide(10, 0)) — это сначала выполнит divide (и упадёт). Правильно: pcall(divide, 10, 0) — передаём функцию и аргументы по отдельности.
  • Игнорировать первое значение ok и сразу использовать второе как результат.
  • Заворачивать в pcall вообще всё подряд, скрывая баги вместо их исправления.

Итог

  • error("текст") намеренно генерирует ошибку с сообщением.
  • pcall(f, аргументы) вызывает функцию под защитой и возвращает ok и результат/ошибку.
  • При ok == false второе значение — текст ошибки; программа не падает.
  • Передавайте функцию и аргументы в pcall по отдельности, а не вызывайте её заранее.
Проверьте себя
1. Что возвращает pcall(f)?
AТолько результат f
BДва значения: успех (true/false) и результат либо текст ошибки
CВсегда nil
DТолько текст ошибки
2. Как правильно защитить вызов divide(10, 0) через pcall?
Apcall(divide(10, 0))
Bpcall(divide, 10, 0)
Cpcall("divide", 10, 0)
Ddivide(pcall, 10, 0)