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.
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)