Coroutines
Coroutines are collaborative threads — independent execution threads that yield control voluntarily. Not preemptive; no parallelism.
Creating and running
-- Create
local co = coroutine.create(function(a, b)
print("body", a, b) -- body 1 10
local r = coroutine.yield(a + b) -- yield 11, resume gets "r"
print("after yield", r) -- after yield hello
return "done"
end)
-- Resume (starts coroutine)
print(coroutine.resume(co, 1, 10)) -- true, 11
print(coroutine.resume(co, "hello")) -- true, "done"
print(coroutine.resume(co)) -- false, "cannot resume dead coroutine"
API
| Function | Description |
|---|---|
coroutine.create(f) | Create new coroutine from function f |
coroutine.resume(co, ...) | Start/resume co. Returns status, values... |
coroutine.yield(...) | Suspend co, return values to resume |
coroutine.wrap(f) | Create coroutine, return iterator function |
coroutine.status(co) | "suspended", "running", "normal", "dead" |
coroutine.isyieldable() | Can the running coroutine yield? |
coroutine.running() | Returns running coroutine + whether it's main |
Status values
| Status | Meaning |
|---|---|
"suspended" | Created or yielded |
"running" | Currently executing |
"normal" | Active but resumed another coroutine |
"dead" | Finished or errored |
local co = coroutine.create(function() coroutine.yield() end)
print(coroutine.status(co)) -- suspended
coroutine.resume(co)
print(coroutine.status(co)) -- suspended (after yield)
coroutine.resume(co)
print(coroutine.status(co)) -- dead
wrap — simplified interface
local f = coroutine.wrap(function()
for i = 1, 3 do
coroutine.yield(i * 10)
end
return 999
end)
print(f()) -- 10
print(f()) -- 20
print(f()) -- 30
print(f()) -- 999
-- f() after that raises error
[!tip]
wrapreturns a function, not a coroutine Noresumeneeded — just call the function. But you lose error handlinghandling.metaXML file that defines vehicle physics properties including speed, acceleration, braking, traction, and suspension behavior. (raises directly instead of returningfalse, msg).
Producer-consumer pattern
function producer()
return coroutine.wrap(function()
for i = 1, 5 do
coroutine.yield(i * 2)
end
end)
end
local get = producer()
for value in get do
print("consumed:", value)
end
-- consumed: 2
-- consumed: 4
-- consumed: 6
-- consumed: 8
-- consumed: 10
Filter pipeline
function filter(source, predicate)
return coroutine.wrap(function()
for item in source do
if predicate(item) then
coroutine.yield(item)
end
end
end)
end
function numbers()
return coroutine.wrap(function()
for i = 1, 20 do coroutine.yield(i) end
end)
end
local evens = filter(numbers(), function(n) return n % 2 == 0 end)
local big_evens = filter(evens, function(n) return n > 10 end)
for n in big_evens do print(n) end
-- 12, 14, 16, 18, 20
Asymmetric coroutines (Lua 5.4)
Lua 5.4LuaA lightweight, embeddable scripting language used as the primary scripting language in FiveM server development. adds coroutine.close() to forcibly close a coroutine:
local co = coroutine.create(function()
coroutine.yield("step 1")
coroutine.yield("step 2")
return "done"
end)
coroutine.resume(co) -- true, "step 1"
coroutine.close(co) -- closes, runs pending finalizers
print(coroutine.status(co)) -- dead
Nested resume (coroutine chain)
local outer = coroutine.create(function()
local inner = coroutine.create(function()
coroutine.yield("from inner")
end)
coroutine.resume(inner) -- resume from inside coroutine
coroutine.yield("from outer")
end)
print(coroutine.resume(outer)) -- true, "from outer"
Error handling in coroutines
Errors don't propagate across resume boundaries:
local co = coroutine.create(function()
error("oops!")
end)
local ok, err = coroutine.resume(co)
print(ok) -- false
print(err) -- stdin:2: oops!
-- coroutine is now dead
[!tip] Use
pcallinside coroutines for finer control:local co = coroutine.create(function()local ok, err = pcall(risky_function)if not ok then coroutine.yield("error: " .. err) endend)
Common patterns
Iterator
function all_words(str)
return coroutine.wrap(function()
for word in str:gmatch("%S+") do
coroutine.yield(word)
end
end)
end
for w in all_words("hello world foo bar") do
print(w)
end
State machine
function traffic_light()
return coroutine.wrap(function()
while true do
coroutine.yield("GREEN")
coroutine.yield("YELLOW")
coroutine.yield("RED")
end
end)
end
local light = traffic_light()
for i = 1, 6 do print(light()) end
-- GREEN YELLOW RED GREEN YELLOW RED
Cooperative multitasking scheduler
local tasks = {}
function spawn(f)
tasks[#tasks + 1] = coroutine.create(f)
end
function scheduler()
while #tasks > 0 do
local co = table.remove(tasks, 1)
if coroutine.status(co) ~= "dead" then
local ok, err = coroutine.resume(co)
if not ok then print("Task error:", err)
elseif coroutine.status(co) ~= "dead" then
tasks[#tasks + 1] = co -- re-queue
end
end
end
end