Modules in Lua

Modules are a way to organize Lua code into reusable, maintainable units. They help you structure larger programs and share functionality between different parts of your application.

What is a Module?

A module is a Lua file that contains related functions, variables, and data that can be loaded and used in other Lua programs:

-- mymodule.lua
local mymodule = {}

function mymodule.hello(name)
    return "Hello, " .. name .. "!"
end

function mymodule.add(a, b)
    return a + b
end

return mymodule

Creating and Using Modules

Basic Module Creation

-- calculator.lua
local calculator = {}

-- Private variables (local to the module)
local version = "1.0"

-- Public functions
function calculator.add(a, b)
    return a + b
end

function calculator.subtract(a, b)
    return a - b
end

function calculator.multiply(a, b)
    return a * b
end

function calculator.divide(a, b)
    if b == 0 then
        error("Division by zero!")
    end
    return a / b
end

-- Function to access private data
function calculator.get_version()
    return version
end

return calculator

Using Modules

-- main.lua
local calc = require("calculator")

local sum = calc.add(10, 5)
print("Sum:", sum)  -- Sum: 15

local product = calc.multiply(4, 7)
print("Product:", product)  -- Product: 28

print("Calculator version:", calc.get_version())  -- Calculator version: 1.0

Module Search Path

Lua searches for modules in the paths defined by package.path:

-- View the current module search path
print(package.path)

-- Typical paths might include:
-- ./?.lua
-- /usr/share/lua/5.4/?.lua
-- /usr/local/share/lua/5.4/?.lua

Module Naming Conventions

-- Different ways to require the same module
local utils = require("utils")           -- utils.lua
local math_lib = require("math_lib")     -- math_lib.lua
local http = require("socket.http")      -- socket/http.lua

Advanced Module Patterns

Module with State

-- counter.lua
local counter = {
    count = 0,
    history = {}
}

function counter.increment(inc)
    inc = inc or 1
    counter.count = counter.count + inc
    table.insert(counter.history, counter.count)
    return counter.count
end

function counter.reset()
    counter.count = 0
    counter.history = {}
end

function counter.get_count()
    return counter.count
end

function counter.get_history()
    return counter.history
end

return counter

Module with Configuration

-- database.lua
local database = {
    config = {
        host = "localhost",
        port = 5432,
        name = "myapp",
        timeout = 30
    },
    connection = nil
}

function database.set_config(new_config)
    for key, value in pairs(new_config) do
        database.config[key] = value
    end
end

function database.connect()
    -- Simulated connection
    database.connection = {
        connected = true,
        config = database.config
    }
    print("Connected to database:", database.config.name)
end

function database.query(sql)
    if not database.connection then
        database.connect()
    end
    print("Executing query:", sql)
    return "Query result"
end

return database

Module with Dependencies

-- user_manager.lua
local json = require("json")  -- Assuming a json module exists
local utils = require("utils")

local user_manager = {
    users = {}
}

function user_manager.add_user(user_data)
    -- Validate using utils module
    if not utils.validate_email(user_data.email) then
        error("Invalid email address")
    end
    
    local user = {
        id = #user_manager.users + 1,
        name = user_data.name,
        email = user_data.email,
        created_at = os.time()
    }
    
    table.insert(user_manager.users, user)
    return user
end

function user_manager.get_user(id)
    for _, user in ipairs(user_manager.users) do
        if user.id == id then
            return user
        end
    end
    return nil
end

function user_manager.export_to_json()
    return json.encode(user_manager.users)
end

return user_manager

Module Best Practices

Private vs Public Members

-- good_module.lua
local M = {}  -- Public module table

-- Private helper function
local function validate_input(input)
    return type(input) == "string" and #input > 0
end

-- Public function that uses private helper
function M.process_string(input)
    if not validate_input(input) then
        error("Invalid input")
    end
    return string.upper(input)
end

-- Private variable
local internal_counter = 0

-- Public function using private variable
function M.get_next_id()
    internal_counter = internal_counter + 1
    return internal_counter
end

return M

Module Initialization

-- config.lua
local M = {}

-- Module initialization function
function M.init(user_config)
    M.config = {
        debug = false,
        timeout = 30,
        retries = 3
    }
    
    -- Merge with user config
    if user_config then
        for key, value in pairs(user_config) do
            M.config[key] = value
        end
    end
end

function M.get_config(key)
    return M.config[key]
end

-- Auto-initialize if not already done
local initialized = false
local function ensure_initialized()
    if not initialized then
        M.init()
        initialized = true
    end
end

function M.debug(message)
    ensure_initialized()
    if M.config.debug then
        print("[DEBUG]", message)
    end
end

return M

Practical Examples

Math Utils Module

-- math_utils.lua
local M = {}

-- Calculate factorial
function M.factorial(n)
    if n <= 1 then
        return 1
    end
    return n * M.factorial(n - 1)
end

-- Generate Fibonacci sequence
function M.fibonacci(n)
    if n <= 0 then return 0 end
    if n == 1 then return 1 end
    
    local a, b = 0, 1
    for i = 2, n do
        a, b = b, a + b
    end
    return b
end

-- Calculate greatest common divisor
function M.gcd(a, b)
    while b ~= 0 do
        a, b = b, a % b
    end
    return a
end

-- Check if number is prime
function M.is_prime(n)
    if n <= 1 then return false end
    if n <= 3 then return true end
    if n % 2 == 0 or n % 3 == 0 then return false end
    
    local i = 5
    while i * i <= n do
        if n % i == 0 or n % (i + 2) == 0 then
            return false
        end
        i = i + 6
    end
    return true
end

return M

File I/O Module

-- file_ops.lua
local M = {}

function M.read_file(path)
    local file = io.open(path, "r")
    if not file then
        error("Cannot open file: " .. path)
    end
    
    local content = file:read("*all")
    file:close()
    return content
end

function M.write_file(path, content)
    local file = io.open(path, "w")
    if not file then
        error("Cannot open file for writing: " .. path)
    end
    
    file:write(content)
    file:close()
end

function M.file_exists(path)
    local file = io.open(path, "r")
    if file then
        file:close()
        return true
    end
    return false
end

function M.get_file_lines(path)
    local lines = {}
    local file = io.open(path, "r")
    if not file then
        return lines
    end
    
    for line in file:lines() do
        table.insert(lines, line)
    end
    
    file:close()
    return lines
end

return M

Using Multiple Modules

-- main_app.lua
local math_utils = require("math_utils")
local file_ops = require("file_ops")
local config = require("config")

-- Initialize configuration
config.init({debug = true, timeout = 60})

-- Process some data
local numbers = {5, 10, 15, 20}
local results = {}

for i, num in ipairs(numbers) do
    table.insert(results, {
        original = num,
        factorial = math_utils.factorial(num),
        is_prime = math_utils.is_prime(num)
    })
    
    config.debug("Processed " .. num)
end

-- Save results to file
local json_data = require("json").encode(results)
file_ops.write_file("results.json", json_data)

print("Processing complete. Results saved to results.json")

Module Management Tips

  1. Use local variables for frequently used modules
  2. Keep modules focused on a single responsibility
  3. Document your module API clearly
  4. Handle errors gracefully in module functions
  5. Version your modules when they might change
  6. Test modules independently before integration

Next Steps

Now that you understand modules, explore LuaRocks to discover and install third-party Lua modules.

For more module information, see the Lua manual.

Last updated on