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