Ошибки в сопрограммах
Что происходит, когда внутри сопрограммы что-то идёт не так? На второй день у Гуго на маршруте случается беда:
-- Гуго потерял карту
local hugo = coroutine.create(function()
coroutine.yield("рынок")
error("Гуго потерял карту")
coroutine.yield("ферма")
end)
print(coroutine.resume(hugo))
print(coroutine.resume(hugo))
print(coroutine.status(hugo))
print(coroutine.resume(hugo))
print("День продолжается.")Он выводит:
true рынок
false main.lua:4: Гуго потерял карту
dead
false cannot resume dead coroutine
День продолжается.Ошибка внутри сопрограммы не останавливает программу, а только саму сопрограмму. С этого момента она dead (до фермы она так и не доберётся), а resume возвращает false и сообщение об ошибке, как и pcall. Возобновление мёртвой сопрограммы тоже не ошибка: вы получите false и cannot resume dead coroutine.
Это удобно: один сломавшийся персонаж не останавливает всю игру. Но это и ловушка: если вы не смотрите, что возвращает resume, ошибка исчезает без следа, а сопрограмма просто тихо перестаёт что-либо делать. Поэтому, если что-то в сопрограмме не происходит, выведите то, что возвращает resume. В готовой программе проверяйте первое значение: после local ok, message = coroutine.resume(hugo) значение ok равно false, если что-то пошло не так, а message говорит, что именно.
Генератор ведёт себя иначе. Функция из coroutine.wrap не возвращает true или false, поэтому она передаёт ошибку дальше, и программа останавливается, как при любой другой ошибке:
-- Гуго снова потерял карту
local nextStop = coroutine.wrap(function()
coroutine.yield("рынок")
error("Гуго потерял карту")
end)
print(nextStop())
print(nextStop())
print("Эта строка не выполнится.")Он выводит:
рынок
main.lua:8: main.lua:4: Гуго потерял картуВ сообщении два номера строки: main.lua:8: — это место, где вызвали генератор, а main.lua:4: — место, где внутри него произошла ошибка. Исчерпанный генератор останавливает программу так же, с сообщением cannot resume dead coroutine. Если нужно поймать ошибку из генератора, вызовите его через pcall, как любую другую функцию: local ok, stop = pcall(nextStop).
Ещё одну ошибку вы уже знаете по этому уроку: coroutine.yield() вне сопрограммы, например в функции, которую вы вызвали напрямую, заканчивается сообщением attempt to yield from outside a coroutine.
Задание
- 1
Гуго каждый день покупает тыкву за 20 монет. Запустите программу: в дни 3 и 4 она выводит ерунду, потому что в эти дни в
coinsлежит не число, а сообщение об ошибке. (error(…, 0)убирает номер строки, как вы знаете из урока о функциях.) Проверьтеok: если тамfalse, выведите вместо этого день и сообщение, напримерДень 3: у Гуго осталось мало монет: 10. Программа выводит:День 1: у Гуго монет: 30 День 2: у Гуго монет: 10 День 3: у Гуго осталось мало монет: 10 День 4: cannot resume dead coroutine - 2
Возобновлять мёртвую сопрограмму бессмысленно. В начале каждого дня проверяйте статус Гуго: если он
dead, выведите день иГуго ушёл домой.вместо возобновления. Программа выводит:День 1: у Гуго монет: 30 День 2: у Гуго монет: 10 День 3: у Гуго осталось мало монет: 10 День 4: Гуго ушёл домой.