Skip to main content

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

FunctionDescription
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

StatusMeaning
"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] wrap returns a function, not a coroutine No resume needed — 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 returning false, 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 pcall inside coroutines for finer control:

local co = coroutine.create(function()
local ok, err = pcall(risky_function)
if not ok then coroutine.yield("error: " .. err) end
end)

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