Coroutines

Lua · Advanced · 14 min

Coroutines drive game scripting, async I/O in OpenResty and Neovim, and elegant generators.

A coroutine is a function with its own stack that can pause itself and be resumed later. It is cooperative: only one runs at a time, and it runs until it explicitly yields.

  • coroutine.create(f) makes a coroutine (type thread) without running it.
  • coroutine.resume(co, ...) starts or continues it. It returns true plus the values passed to yield (or returned at the end), or false, err if the coroutine raised an error.
  • coroutine.yield(...) suspends the current coroutine; the values become resume's results, and the arguments of the next resume become yield's return values.
  • coroutine.status(co) is suspended, running, normal or dead.
  • coroutine.wrap(f) returns a function that resumes on each call and re-raises errors, perfect for generators used in a generic for.

Because the stack is preserved, a coroutine can yield from deep inside nested calls. That lets you write sequential-looking code (wait for an animation, then for input, then for a timer) while a scheduler resumes the coroutine when each event happens. OpenResty and many Neovim plugins turn callback APIs into straight-line code exactly this way.

coroutine.close(co) (5.4) kills a suspended coroutine and runs its pending to-be-closed variables. Yielding across a C call boundary only works when the C function supports continuations.

Example

local function numbers(limit)
  return coroutine.wrap(function()
    for i = 1, limit do coroutine.yield(i, i * i) end
  end)
end
for n, sq in numbers(3) do io.write(n, ":", sq, " ") end
print()                               --> 1:1 2:4 3:9

local co = coroutine.create(function(a)
  print("got", a)                     --> got	1
  local b = coroutine.yield(a + 1)
  print("got", b)                     --> got	10
  return "done"
end)
print(coroutine.resume(co, 1))        --> true	2
print(coroutine.status(co))           --> suspended
print(coroutine.resume(co, 10))       --> true	done
print(coroutine.status(co))           --> dead
print(coroutine.resume(co))           --> false	cannot resume dead coroutine

Remember

  • resume passes values in; yield passes values out, and vice versa
  • coroutine.wrap gives a function, ideal for generators
  • Coroutines are cooperative and keep their whole call stack

Watch out for

  • Ignoring resume's first return value: errors inside a coroutine are returned, not raised.

Check yourself

  1. A coroutine raises an error. What does coroutine.resume return?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.lua.org/manual/5.4/manual.html#2.6