25. Modules and require

Every script you have written so far has lived in one file. Real programs grow past that. Lua splits programs across files using modules: a module is a Lua file that exposes a few related functions for other files to use.

A module is just a file that returns a table

The convention is short:

-- file: greet.lua
local M = {}

function M.hello(name)
    print("Hello, " .. name .. "!")
end

function M.bye(name)
    print("Goodbye, " .. name .. ".")
end

return M

Three things to notice:

  • local M = {} creates an empty table called M (any name works, but M is the convention).
  • function M.hello(name) ... end adds a function under the key hello — the dictionary syntax from chapter 23 in another shape.
  • return M at the end is how the module exposes its table to whoever loads it.

The file does no other work when loaded. Nothing prints, nothing runs. It just defines functions and hands a table back to the caller.

Using a module with require

Another file loads it with require:

-- file: main.lua
local greet = require("greet")

greet.hello("Keiko")
greet.bye("Keiko")

require("greet") does three things:

  1. Finds a file called greet.lua on the Lua search path.
  2. Runs it (only the first time — see the box below).
  3. Returns whatever the file returned.

So local greet = require("greet") gives you the module's table. From it you call greet.hello, greet.bye, etc.

require caches its result. If two files both require("greet"), Lua loads greet.lua once and hands both the same table. Calling require("greet") ten times still loads it once. This is good — it means modules are cheap to require from anywhere.

Where Lua looks

Out of the box, Lua looks in the current working directory and a few system paths. The simplest layout — and the only one you need right now — is to put main.lua and greet.lua in the same folder, then cd into it before running main.lua:

cd exercises/25/examples
lua main.lua

If you stay at the repo root and try lua exercises/25/examples/main.lua, require("greet") may fail to find greet.lua, because the current directory is the repo root, not the example folder.

In Roblox, require works differently — it loads a ModuleScript from the game's data tree instead of a file on disk. The shape of the module (local M = {} ... return M) is identical.

Local vs global inside a module

Variables and functions declared with local inside a module file are private to that file — handy for helper functions the module does not want to expose:

-- file: math_helpers.lua
local M = {}

local function clamp(x, lo, hi)
    if x < lo then return lo end
    if x > hi then return hi end
    return x
end

function M.double(x)
    return clamp(x * 2, -100, 100)
end

return M

clamp is local and not on M, so other files cannot call math_helpers.clamp — only the module's own functions can. That is how you build a clean, small public surface.

Homework

The Part 5 homework uses two files per problem: a module file and a main.lua that uses it. Each problem lives in its own sub-folder so the two files sit together. Open a terminal, cd into that folder, then run lua main.lua.

Problem 1 — Greet module

Folder: exercises/25/homework/01-greet-module/.

Finish greet.lua so it exposes M.hello(name) and M.bye(name). The main.lua file is provided and already calls both functions.

Problem 2 — Math helpers

Folder: exercises/25/homework/02-math-helpers/.

Build a math_helpers.lua module with at least two functions:

  • M.double(x) returns x * 2.
  • M.triple(x) returns x * 3.

main.lua calls both and prints the results.

Problem 3 — Counter

Folder: exercises/25/homework/03-counter/.

Build a counter.lua module that exposes three functions:

  • M.increment() — increases the counter by 1.
  • M.get() — returns the current value.
  • M.reset() — sets the counter back to 0.

The counter itself is a local variable inside the module. It must persist across calls (think back to the scope discussion at the end of chapter 21).

Challenge — String utilities

Folder: exercises/25/homework/04-string-utils/.

Build a string_utils.lua module with at least three functions of your own. Suggestions:

  • M.shout(s) returns s in upper case with ! appended.
  • M.echo(s, n) returns s repeated n times with spaces between.
  • M.reverse_words(s) returns the words of s in reverse order.

The last one is a stretch — you will need to walk the string and collect words. If that is too much, swap it for anything else of your own.

Stuck or finished? Open the homework solutions page.