Skip to content

Modules

Lesson 6.4

A module is a Lua file that returns a table of functions and values. With require you split a program into small files that each do one job.

A module is a file that returns a table

Create a file called inventory.lua:

-- inventory.lua
local M = {}

local MAX_STACK = 64   -- private: other files can't see it

function M.stacks(count)
  return math.ceil(count / MAX_STACK)
end

function M.describe(item, count)
  return string.format("%d x %s (%d stacks)", count, item, M.stacks(count))
end

return M

The module builds a table (by habit called M), puts its public functions in it, and returns it at the end. function M.stacks(count) is short for M.stacks = function(count) ... end.

Locals in the file, such as MAX_STACK, stay private to the module. Only what you put in the returned table is visible from outside.

Using a module

In another file in the same folder, load it with require:

-- main.lua
local inventory = require("inventory")

print(inventory.describe("Cobblestone", 200))  --> 200 x Cobblestone (4 stacks)

require("inventory") finds inventory.lua, runs it, and gives you what it returned. Store the result in a local with the same name. The brackets are optional when the only argument is a string, so you will often see require "inventory".

Modules load once

require runs a file only the first time. It saves the result in the table package.loaded, and later calls return that same table:

local a = require("inventory")
local b = require("inventory")
print(a == b)  --> true

So a module is a good place for shared state, like a settings table that several files read.

Folders and dots

Dots in the name map to folders. require("util.text") loads util/text.lua. Where Lua looks is controlled by package.path, a list of patterns such as ./?.lua, where ? is replaced by the module name. Usually you don’t need to change it.

In Celestial

In a Celestial mod, require only finds files inside your mod’s own scripts/ folder, and module names start with your mod’s id: require "my_mod.util.text" loads scripts/util/text.lua. Part 2 shows the layout of a mod.

Testing a module without files

You can try modules in the online demo, where there is only one file, by registering a module function by hand in package.preload. require checks there before it looks for files:

package.preload["greetings"] = function()
  local M = {}
  function M.hello(name)
    return "Hello, " .. name
  end
  return M
end

local greetings = require("greetings")
print(greetings.hello("Kai"))  --> Hello, Kai

You won’t need this in real projects, but it is handy for experiments.

Avoid circular requires

If a.lua requires b.lua and b.lua requires a.lua, one of them gets a module that hasn’t finished loading. Keep dependencies going one way: small helper modules at the bottom, the main file at the top. If two modules need each other, move the shared part into a third module.

Good module habits

  • One topic per module: inventory.lua, teams.lua, text.lua.
  • Keep helpers local and only export what other files need.
  • Don’t create globals in a module. Return everything in the table.
  • Return the table at the very end of the file.

Try it

Write a module dice (use package.preload so it runs in one file) with a function roll(sides) that returns a random number from 1 to sides, and a function roll_many(count, sides) that returns a list of rolls. Require it and print five rolls of a six-sided die, joined with spaces.

Show a solution
package.preload["dice"] = function()
  local M = {}

  function M.roll(sides)
    return math.random(1, sides)
  end

  function M.roll_many(count, sides)
    local rolls = {}
    for i = 1, count do
      rolls[i] = M.roll(sides)
    end
    return rolls
  end

  return M
end

local dice = require("dice")
print(table.concat(dice.roll_many(5, 6), " "))

The numbers are random, so each run prints something different, for example 3 6 1 1 4.

Recap: a module is a file that returns a table, require loads it once and caches it, and locals inside it stay private.