Reading — step 1 of 5
Learn
Modules: local tables, return, require
Every program in this course has been a single file. Real Lua never is: a Neovim plugin or an OpenResty handler is a pile of files that load one another. The mechanism is smaller than you would guess. A module is a table, and loading one is a function call.
The shape
A module file builds a local table, hangs functions off it, and returns it as the last statement of the file.
-- textutil.lua
local M = {}
function M.trim(s)
return (s:gsub("^%s+", ""):gsub("%s+$", ""))
end
function M.words(s)
local out = {}
for w in s:gmatch("%S+") do out[#out + 1] = w end
return out
end
return M
No new machinery. M is an ordinary table, M.trim an ordinary field holding an ordinary function, and return works at the top of a file because a Lua file is a function — the manual calls it a chunk. The parentheses around the trim body matter for the reason String Patterns gave: gsub returns two values, and without them the caller would get the substitution count too.
local is the whole privacy story
Leave local off and the name becomes a global — reachable from, and overwritable by, every other file in the process.
helper = function() return 1 end -- global: lands in _G
local function helper2() return 2 end -- local: stays in this file
print(type(_G.helper), type(_G.helper2))
--> function nil
A helper you never meant to publish stays invisible until a second module picks the same name. Declare everything local, and publish deliberately by putting it on M.
require
Another file then loads the module by name:
local textutil = require("textutil")
print(textutil.trim(" hi "))
require searches package.path for textutil.lua, runs it once, and returns whatever the chunk returned. Ask again and the file is not re-read: the result is cached in package.loaded, so every file that requires a module shares one copy of it.
Grader note: those two lines cannot run here. This editor submits one file, so there is no
textutil.luato find, and the submission dies before printing anything.
Output — the search list is twelve paths, trimmed to four here:
/usr/local/lua-5.3.5/lua53: script.lua:1: module 'textutil' not found:
no field package.preload['textutil']
no file '/usr/local/share/lua/5.3/textutil.lua'
no file './textutil.lua'
no file './textutil.so'
Everything else on this page works in one file. Write your modules as local tables now; require is the line you add the day the project grows a second file.
Watching require work anyway
Read the first line of that error again: no field package.preload['textutil']. Before it touches the filesystem, require consults a table of loader functions — and you may put one there yourself, which is enough to watch the real contract without a second file.
local loads = 0
package.preload["greet"] = function()
loads = loads + 1
return {hi = function(name) return "hi " .. name end}
end
local g = require("greet")
require("greet")
require("greet")
print(g.hi("ada"), loads, package.loaded["greet"] == g)
--> hi ada 1 true
Three calls, one load, and package.loaded holding the very table the loader returned. That caching is the point of require: whatever a module does at load time — read a config file, build a lookup table — happens exactly once, however many files ask for it.
Common mistakes
- Forgetting the final
return—requirestorestrueinstead of your table, and the caller dies onattempt to index a boolean value. - Leaving off
local— every helper becomes a global, and two modules that each definesplitoverwrite one another in silence. - Requiring a filename —
require("textutil.lua")searches fortextutil/lua.lua, because a dot in a module name is a directory separator. - Expecting
requireto re-read the file — it caches, so a module's top-level code runs once per process and never again. - Exporting by assigning to
_G— the returned table is the export list; anything off it should stay unreachable.
Your exercise
Text Kit builds a module in one file and then uses it as data. You write three functions on a local table textkit, and the driver looks each command up in that table with textkit[op] — possible only because a module is an ordinary table.
Two things the grader will catch. The functions must return, not print: the driver does the printing, so a textkit.words that calls print itself emits the count and then a blank line from the driver's own print. And the closing exports: line lists the keys alphabetically — they come out of pairs, whose order is unspecified, so collect them into a list and table.sort it before joining.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…