Web Analytics

Coroutines in Lua

Advanced ~25 min read

Coroutines are one of Lua's most powerful features, enabling cooperative multitasking and sophisticated control flow. Unlike threads, coroutines are cooperativeβ€”they explicitly yield control to other coroutines. In this lesson, you'll learn how to create, manage, and use coroutines effectively. Let's explore this advanced feature!

What are Coroutines?

A coroutine is like a function that can pause and resume execution:

-- Create a coroutine
local co = coroutine.create(function()
    print("Hello")
    coroutine.yield()
    print("World")
end)

-- Resume the coroutine
coroutine.resume(co)  -- Prints: Hello
coroutine.resume(co)  -- Prints: World
Key Concepts:
  • Create: coroutine.create(func) creates a coroutine
  • Resume: coroutine.resume(co) starts/resumes execution
  • Yield: coroutine.yield() pauses execution
Output
Click Run to execute your code

Coroutine States

A coroutine can be in one of four states:

local co = coroutine.create(function()
    print("Running")
    coroutine.yield()
    print("Done")
end)

print(coroutine.status(co))  -- suspended

coroutine.resume(co)
print(coroutine.status(co))  -- suspended (yielded)

coroutine.resume(co)
print(coroutine.status(co))  -- dead
State Description
suspended Created but not started, or yielded
running Currently executing
normal Resumed another coroutine
dead Finished execution or error

Passing Values

Resume to Yield

local co = coroutine.create(function(a, b)
    print("Received:", a, b)
    local x, y = coroutine.yield(a + b)
    print("Received after yield:", x, y)
    return x * y
end)

local success, result = coroutine.resume(co, 10, 20)
print("First resume:", result)  -- 30

local success, result = coroutine.resume(co, 5, 6)
print("Second resume:", result)  -- 30

Yield to Resume

local co = coroutine.create(function()
    for i = 1, 5 do
        coroutine.yield(i)
    end
end)

while coroutine.status(co) ~= "dead" do
    local success, value = coroutine.resume(co)
    if success and value then
        print("Yielded:", value)
    end
end
Output
Click Run to execute your code

Coroutine Patterns

Producer-Consumer

local function producer()
    return coroutine.create(function()
        for i = 1, 10 do
            coroutine.yield(i)
        end
    end)
end

local function consumer(prod)
    while true do
        local success, value = coroutine.resume(prod)
        if not success or not value then
            break
        end
        print("Consumed:", value)
    end
end

local prod = producer()
consumer(prod)

Iterator with Coroutines

local function range(from, to)
    return coroutine.wrap(function()
        for i = from, to do
            coroutine.yield(i)
        end
    end)
end

-- Use like a regular iterator
for i in range(1, 5) do
    print(i)  -- 1, 2, 3, 4, 5
end
Tip: coroutine.wrap() creates a coroutine and returns a function that resumes it. It's simpler than create() + resume() for iterators.

Practical Examples

Task Scheduler

local Scheduler = {}

function Scheduler:new()
    local self = {tasks = {}}
    setmetatable(self, {__index = Scheduler})
    return self
end

function Scheduler:add(func)
    table.insert(self.tasks, coroutine.create(func))
end

function Scheduler:run()
    while #self.tasks > 0 do
        for i = #self.tasks, 1, -1 do
            local task = self.tasks[i]
            local success, err = coroutine.resume(task)
            
            if not success then
                print("Task error:", err)
                table.remove(self.tasks, i)
            elseif coroutine.status(task) == "dead" then
                table.remove(self.tasks, i)
            end
        end
    end
end

-- Usage
local scheduler = Scheduler:new()

scheduler:add(function()
    for i = 1, 3 do
        print("Task 1:", i)
        coroutine.yield()
    end
end)

scheduler:add(function()
    for i = 1, 3 do
        print("Task 2:", i)
        coroutine.yield()
    end
end)

scheduler:run()

Async File Reader

local function asyncReadFile(filename)
    return coroutine.create(function()
        local file = io.open(filename, "r")
        if not file then
            return nil, "File not found"
        end
        
        while true do
            local line = file:read("*line")
            if not line then break end
            coroutine.yield(line)
        end
        
        file:close()
    end)
end

-- Usage
local reader = asyncReadFile("data.txt")
while coroutine.status(reader) ~= "dead" do
    local success, line = coroutine.resume(reader)
    if success and line then
        print("Line:", line)
    end
end

State Machine

local function stateMachine()
    return coroutine.create(function()
        -- State 1: Idle
        print("State: Idle")
        local input = coroutine.yield()
        
        -- State 2: Processing
        if input == "start" then
            print("State: Processing")
            coroutine.yield()
            
            -- State 3: Complete
            print("State: Complete")
        else
            print("State: Error")
        end
    end)
end

local sm = stateMachine()
coroutine.resume(sm)
coroutine.resume(sm, "start")
coroutine.resume(sm)
Output
Click Run to execute your code

coroutine.wrap() vs coroutine.create()

Feature create() wrap()
Returns Coroutine object Function
Resume coroutine.resume(co) func()
Error handling Returns success, result Propagates errors
Use case When you need error handling Simple iterators

Practice Exercise

Try these coroutine challenges:

Output
Click Run to execute your code

Summary

In this lesson, you learned:

  • What coroutines are and how they work
  • Creating coroutines with coroutine.create()
  • Resuming and yielding with resume() and yield()
  • Coroutine states (suspended, running, normal, dead)
  • Passing values between resume and yield
  • Common patterns (producer-consumer, iterators)
  • Practical examples (scheduler, async reader, state machine)
  • Difference between create() and wrap()

What's Next?

You've mastered coroutines! Next, we'll explore file I/Oβ€”how to read and write files, handle different file modes, and work with the file system. Let's continue! πŸš€