Use when lua coroutines for cooperative multitasking including coroutine creation, yielding and resuming, passing values, generators, iterators, asynchronous patterns, state machines, and producer-consumer implementations.
Read-only skill
Additional assets for this skill
This skill cannot use any tools. It operates in read-only mode without the ability to modify files or execute commands.
Coroutines in Lua provide cooperative multitasking, enabling functions to suspend and resume execution. Unlike threads, coroutines don't run in parallel but yield control explicitly, making them simpler to reason about while enabling powerful asynchronous patterns without callback complexity.
Coroutines are first-class values in Lua, created from functions and managed through the coroutine library. They maintain their own stack, local variables, and instruction pointer, allowing suspension at any point and resumption later. This enables elegant implementations of generators, iterators, and state machines.
This skill covers coroutine basics, yielding and resuming with values, generators and iterators, producer-consumer patterns, asynchronous I/O simulation, state machines, error handling, and practical coroutine patterns.
Coroutines enable functions to pause and resume execution, providing cooperative multitasking without thread complexity.
-- Creating a coroutine
local function simple_task()
print("Task started")
coroutine.yield()
print("Task resumed")
coroutine.yield()
print("Task finished")
end
local co = coroutine.create(simple_task)
-- Checking coroutine status
print(coroutine.status(co)) -- "suspended"
-- Resuming coroutine
coroutine.resume(co) -- Prints "Task started"
print(coroutine.status(co)) -- "suspended"
coroutine.resume(co) -- Prints "Task resumed"
coroutine.resume(co) -- Prints "Task finished"
print(coroutine.status(co)) -- "dead"
-- Coroutine with parameters
local function greet(name)
print("Hello, " .. name)
local response = coroutine.yield("What's your age?")
print(name .. " is " .. response .. " years old")
end
local co2 = coroutine.create(greet)
local success, question = coroutine.resume(co2, "Alice")
print(question) -- "What's your age?"
coroutine.resume(co2, 30) -- "Alice is 30 years old"
-- Coroutine returning values
local function counter()
for i = 1, 5 do
coroutine.yield(i)
end
return "done"
end
local co3 = coroutine.create(counter)
repeat
local success, value = coroutine.resume(co3)
print(value)
until coroutine.status(co3) == "dead"
-- Coroutine wrap (simpler interface)
local function wrapped_task()
for i = 1, 3 do
coroutine.yield(i * 10)
end
end
local f = coroutine.wrap(wrapped_task)
print(f()) -- 10
print(f()) -- 20
print(f()) -- 30
-- Running coroutine
local function self_aware()
if coroutine.running() then
print("Running in coroutine")
else
print("Running in main")
end
end
self_aware() -- "Running in main"
coroutine.resume(coroutine.create(self_aware)) -- "Running in coroutine"
-- Yielding from nested calls
local function inner()
print("Inner start")
coroutine.yield("from inner")
print("Inner end")
end
local function outer()
print("Outer start")
inner()
print("Outer end")
end
local co4 = coroutine.create(outer)
coroutine.resume(co4) -- Prints "Outer start" and "Inner start"
coroutine.resume(co4) -- Prints "Inner end" and "Outer end"
-- Bidirectional communication
local function echo()
while true do
local value = coroutine.yield()
if value == nil then break end
print("Echo: " .. value)
end
end
local co5 = coroutine.create(echo)
coroutine.resume(co5)
coroutine.resume(co5, "Hello")
coroutine.resume(co5, "World")
coroutine.resume(co5) -- nil terminates
-- Error handling in coroutines
local function faulty()
print("Before error")
error("Something went wrong")
print("After error") -- Never executes
end
local co6 = coroutine.create(faulty)
local success, err = coroutine.resume(co6)
if not success then
print("Error caught: " .. err)
end
Coroutines enable cooperative multitasking where functions explicitly yield control rather than being preempted.
Coroutines elegantly implement generators and custom iterators for lazy evaluation and infinite sequences.
-- Simple generator
local function range(from, to, step)
step = step or 1
return coroutine.wrap(function()
for i = from, to, step do
coroutine.yield(i)
end
end)
end
for n in range(1, 10, 2) do
print(n) -- 1, 3, 5, 7, 9
end
-- Infinite generator
local function naturals()
return coroutine.wrap(function()
local n = 1
while true do
coroutine.yield(n)
n = n + 1
end
end)
end
local gen = naturals()
print(gen()) -- 1
print(gen()) -- 2
print(gen()) -- 3
-- Fibonacci generator
local function fibonacci()
return coroutine.wrap(function()
local a, b = 0, 1
while true do
coroutine.yield(a)
a, b = b, a + b
end
end)
end
local fib = fibonacci()
for i = 1, 10 do
print(fib())
end
-- Filter generator
local function filter(gen, predicate)
return coroutine.wrap(function()
for value in gen do
if predicate(value) then
coroutine.yield(value)
end
end
end)
end
local evens = filter(range(1, 20), function(n) return n % 2 == 0 end)
for n in evens do
print(n) -- 2, 4, 6, 8, 10, 12, 14, 16, 18, 20
end
-- Map generator
local function map(gen, transform)
return coroutine.wrap(function()
for value in gen do
coroutine.yield(transform(value))
end
end)
end
local squared = map(range(1, 5), function(n) return n * n end)
for n in squared do
print(n) -- 1, 4, 9, 16, 25
end
-- Take generator (limit results)
local function take(gen, n)
return coroutine.wrap(function()
local count = 0
for value in gen do
if count >= n then break end
coroutine.yield(value)
count = count + 1
end
end)
end
local first5 = take(naturals(), 5)
for n in first5 do
print(n) -- 1, 2, 3, 4, 5
end
-- Chain generators
local function chain(...)
local generators = {...}
return coroutine.wrap(function()
for _, gen in ipairs(generators) do
for value in gen do
coroutine.yield(value)
end
end
end)
end
local combined = chain(range(1, 3), range(10, 12))
for n in combined do
print(n) -- 1, 2, 3, 10, 11, 12
end
-- Zip generators
local function zip(gen1, gen2)
return coroutine.wrap(function()
while true do
local v1 = gen1()
local v2 = gen2()
if v1 == nil or v2 == nil then break end
coroutine.yield(v1, v2)
end
end)
end
local letters = coroutine.wrap(function()
for c in string.gmatch("abc", ".") do
coroutine.yield(c)
end
end)
local zipped = zip(range(1, 3), letters)
for num, letter in zipped do
print(num, letter) -- 1 a, 2 b, 3 c
end
-- Permutation generator
local function permute(array)
return coroutine.wrap(function()
local function perm(arr, n)
n = n or #arr
if n == 1 then
coroutine.yield(arr)
else
for i = 1, n do
arr[n], arr[i] = arr[i], arr[n]
perm(arr, n - 1)
arr[n], arr[i] = arr[i], arr[n]
end
end
end
local copy = {}
for i, v in ipairs(array) do
copy[i] = v
end
perm(copy)
end)
end
for perm in permute({1, 2, 3}) do
print(table.concat(perm, ", "))
end
-- File line iterator
local function lines(filename)
return coroutine.wrap(function()
local file = io.open(filename, "r")
if not file then return end
for line in file:lines() do
coroutine.yield(line)
end
file:close()
end)
end
Generators enable lazy evaluation and infinite sequences with clean, readable syntax.
Coroutines elegantly implement producer-consumer patterns without explicit queues or callbacks.
-- Basic producer-consumer
local function producer()
return coroutine.create(function()
for i = 1, 10 do
print("Producing " .. i)
coroutine.yield(i)
end
end)
end
local function consumer(prod)
while coroutine.status(prod) ~= "dead" do
local success, value = coroutine.resume(prod)
if success and value then
print("Consuming " .. value)
end
end
end
local prod = producer()
consumer(prod)
-- Filtered producer-consumer
local function filtered_producer(filter_fn)
return coroutine.create(function()
for i = 1, 20 do
if filter_fn(i) then
coroutine.yield(i)
end
end
end)
end
local even_prod = filtered_producer(function(n) return n % 2 == 0 end)
consumer(even_prod)
-- Multiple consumers
local function multi_consumer(prod, num_consumers)
local consumers = {}
for i = 1, num_consumers do
consumers[i] = coroutine.create(function()
while true do
local success, value = coroutine.resume(prod)
if not success or value == nil then break end
print(string.format("Consumer %d got %d", i, value))
coroutine.yield()
end
end)
end
-- Round-robin scheduling
local active = true
while active do
active = false
for _, consumer in ipairs(consumers) do
if coroutine.status(consumer) ~= "dead" then
coroutine.resume(consumer)
active = true
end
end
end
end
-- Pipeline pattern
local function pipeline(...)
local stages = {...}
return function(input)
local current = input
for _, stage in ipairs(stages) do
local co = coroutine.create(stage)
local results = {}
for value in current do
local success, result = coroutine.resume(co, value)
if success and result then
table.insert(results, result)
end
end
-- Convert results to generator
current = coroutine.wrap(function()
for _, v in ipairs(results) do
coroutine.yield(v)
end
end)
end
return current
end
end
-- Data processing pipeline
local function double(n)
coroutine.yield(n * 2)
end
local function add_ten(n)
coroutine.yield(n + 10)
end
local process = pipeline(double, add_ten)
local result = process(range(1, 5))
for n in result do
print(n) -- 12, 14, 16, 18, 20
end
-- Task scheduler with priorities
local Scheduler = {}
function Scheduler.new()
return {
tasks = {},
current = 1
}
end
function Scheduler.add(scheduler, priority, task_fn)
table.insert(scheduler.tasks, {
priority = priority,
coroutine = coroutine.create(task_fn)
})
table.sort(scheduler.tasks, function(a, b)
return a.priority > b.priority
end)
end
function Scheduler.run(scheduler)
while #scheduler.tasks > 0 do
local task = scheduler.tasks[1]
local success, result = coroutine.resume(task.coroutine)
if coroutine.status(task.coroutine) == "dead" then
table.remove(scheduler.tasks, 1)
else
-- Move to end for round-robin
table.remove(scheduler.tasks, 1)
table.insert(scheduler.tasks, task)
end
end
end
-- Usage
local sched = Scheduler.new()
Scheduler.add(sched, 1, function()
for i = 1, 3 do
print("Low priority task " .. i)
coroutine.yield()
end
end)
Scheduler.add(sched, 10, function()
for i = 1, 3 do
print("High priority task " .. i)
coroutine.yield()
end
end)
Scheduler.run(sched)
Producer-consumer patterns with coroutines eliminate callback complexity and provide clear data flow.
Coroutines enable asynchronous I/O patterns without callbacks, providing sequential-looking code for async operations.
-- Async timer simulation
local Async = {}
function Async.sleep(seconds)
local wake_time = os.time() + seconds
coroutine.yield(wake_time)
end
function Async.run(tasks)
local waiting = {}
-- Initialize tasks
for _, task_fn in ipairs(tasks) do
local co = coroutine.create(task_fn)
table.insert(waiting, {coroutine = co, wake_time = 0})
end
-- Event loop
while #waiting > 0 do
local current_time = os.time()
local still_waiting = {}
for _, task in ipairs(waiting) do
if current_time >= task.wake_time then
local success, wake_time = coroutine.resume(task.coroutine)
if coroutine.status(task.coroutine) ~= "dead" then
table.insert(still_waiting, {
coroutine = task.coroutine,
wake_time = wake_time or 0
})
end
else
table.insert(still_waiting, task)
end
end
waiting = still_waiting
if #waiting > 0 then
-- Sleep briefly to avoid busy waiting
os.execute("sleep 0.1")
end
end
end
-- Usage
Async.run({
function()
print("Task 1 start")
Async.sleep(1)
print("Task 1 middle")
Async.sleep(1)
print("Task 1 end")
end,
function()
print("Task 2 start")
Async.sleep(2)
print("Task 2 end")
end
})
-- HTTP request simulation
local function http_get(url)
-- Simulate async HTTP request
coroutine.yield("waiting_for_" .. url)
return "Response from " .. url
end
local function fetch_multiple()
local urls = {
"http://api.example.com/users",
"http://api.example.com/posts",
"http://api.example.com/comments"
}
local results = {}
for _, url in ipairs(urls) do
local response = http_get(url)
table.insert(results, response)
end
return results
end
-- Promise-like pattern
local Promise = {}
Promise.__index = Promise
function Promise.new(executor)
local self = setmetatable({
state = "pending",
value = nil,
callbacks = {}
}, Promise)
local function resolve(value)
if self.state == "pending" then
self.state = "fulfilled"
self.value = value
for _, callback in ipairs(self.callbacks) do
callback(value)
end
end
end
coroutine.resume(coroutine.create(function()
executor(resolve)
end))
return self
end
function Promise:andThen(callback)
if self.state == "fulfilled" then
callback(self.value)
else
table.insert(self.callbacks, callback)
end
return self
end
-- Usage
local p = Promise.new(function(resolve)
-- Simulate async work
coroutine.yield()
resolve(42)
end)
p:andThen(function(value)
print("Resolved: " .. value)
end)
-- Async/await pattern
local function async(fn)
return function(...)
local args = {...}
return coroutine.create(function()
fn(table.unpack(args))
end)
end
end
local function await(co)
local success, result = coroutine.resume(co)
return result
end
local fetch_user = async(function(id)
print("Fetching user " .. id)
coroutine.yield()
return {id = id, name = "User " .. id}
end)
local main = async(function()
local user = await(fetch_user(1))
print("Got user: " .. user.name)
end)
coroutine.resume(main())
Async patterns with coroutines provide sequential code style for asynchronous operations without callback nesting.
Coroutines naturally implement state machines with clean state transitions and local state preservation.
-- Connection state machine
local function connection_state_machine()
local state = "disconnected"
return coroutine.wrap(function()
while true do
local event = coroutine.yield(state)
if state == "disconnected" then
if event == "connect" then
print("Connecting...")
state = "connecting"
end
elseif state == "connecting" then
if event == "connected" then
print("Connected!")
state = "connected"
elseif event == "error" then
print("Connection failed")
state = "disconnected"
end
elseif state == "connected" then
if event == "disconnect" then
print("Disconnecting...")
state = "disconnecting"
elseif event == "send" then
print("Sending data...")
end
elseif state == "disconnecting" then
if event == "disconnected" then
print("Disconnected")
state = "disconnected"
end
end
end
end)
end
local conn = connection_state_machine()
print(conn("connect")) -- "connecting"
print(conn("connected")) -- "connected"
print(conn("send")) -- "connected"
print(conn("disconnect")) -- "disconnecting"
print(conn("disconnected")) -- "disconnected"
-- Parser state machine
local function parse_json_string()
return coroutine.wrap(function()
local chars = {}
local escaped = false
while true do
local char = coroutine.yield()
if char == '"' and not escaped then
break
elseif char == '\\' and not escaped then
escaped = true
else
table.insert(chars, char)
escaped = false
end
end
return table.concat(chars)
end)
end
-- Game AI state machine
local function enemy_ai()
local health = 100
local target = nil
return coroutine.wrap(function()
local state = "idle"
while health > 0 do
local input = coroutine.yield(state)
if state == "idle" then
if input.event == "player_spotted" then
target = input.player
state = "chase"
end
elseif state == "chase" then
if input.event == "player_in_range" then
state = "attack"
elseif input.event == "player_lost" then
target = nil
state = "idle"
end
elseif state == "attack" then
if input.event == "attack" then
print("Enemy attacks!")
elseif input.event == "player_out_of_range" then
state = "chase"
elseif input.event == "damaged" then
health = health - input.damage
if health < 30 then
state = "flee"
end
end
elseif state == "flee" then
if input.event == "safe_distance" then
state = "idle"
end
end
end
return "dead"
end)
end
-- Traffic light state machine
local 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()) -- green, yellow, red, green, yellow, red
end
-- Dialog system
local function dialog_tree(tree)
return coroutine.wrap(function()
local current = tree.start
while current do
local node = tree.nodes[current]
coroutine.yield(node.text, node.choices)
local choice = coroutine.yield()
if node.choices and node.choices[choice] then
current = node.choices[choice].next
else
current = nil
end
end
end)
end
local dialog = dialog_tree({
start = "greeting",
nodes = {
greeting = {
text = "Hello, traveler!",
choices = {
{text = "Hello", next = "ask_quest"},
{text = "Goodbye", next = nil}
}
},
ask_quest = {
text = "Need a quest?",
choices = {
{text = "Yes", next = "give_quest"},
{text = "No", next = nil}
}
},
give_quest = {
text = "Find the lost sword!",
choices = {}
}
}
})
State machines with coroutines maintain state naturally without complex state tracking structures.
Use coroutine.wrap for iterators as it provides simpler interface without status checking
Check coroutine.resume return values to handle errors and detect completion
Avoid yielding across C boundaries as it's not supported in standard Lua
Pass data through yield and resume rather than using global or upvalue variables
Use coroutine.status to check if coroutine is dead before resuming
Create generators with coroutine.wrap for clean iteration syntax
Implement proper cleanup in coroutines using pcall for error handling
Avoid nested coroutine.resume calls as they complicate control flow
Use coroutine.running to check execution context and avoid invalid yields
Document yield points clearly to help readers understand suspension points
Yielding from main thread causes errors as main is not a coroutine
Not checking resume success misses errors thrown inside coroutines
Creating new coroutines in loops without cleanup causes memory leaks
Yielding across C call boundaries fails in standard Lua (works in LuaJIT)
Assuming coroutines are threads leads to race condition concerns that don't exist
Not handling coroutine completion causes errors when resuming dead coroutines
Overusing coroutines for simple iteration adds complexity without benefits
Mixing coroutine.create and wrap interfaces causes confusion
Forgetting to resume coroutines in schedulers leaves tasks suspended forever
Passing wrong number of arguments to resume causes unexpected behavior
Apply coroutines for cooperative multitasking where explicit control flow is beneficial.
Use generators and iterators when implementing lazy evaluation or infinite sequences.
Leverage coroutines for async I/O patterns to avoid callback complexity and maintain sequential code style.
Implement state machines with coroutines for game AI, parsers, or protocol handlers.
Use producer-consumer patterns when processing data through transformation pipelines.
Apply coroutine-based schedulers for managing multiple concurrent operations cooperatively.