There’s often confusion as to what exactly happens when external modules are “required” into your code, which leads to further confusion and unexpected behavior when it comes to things such as Composer scenes or even custom modules of your own.
This tutorial will guide you through a series of exercises (with explanations) that should illustrate exactly how modules work in Lua, so you get a full understanding of when (and which) code in your modules is executed.
In its simplest form, an external module is simply a Lua file that returns something, most likely a table. It can almost be thought of as a function definition, but it’s contained in a file all to itself.
Here’s a really simple module, example1.lua, which simply prints a statement to the console and returns an empty table:
-- example1.lua
local M = {}
print( "example1.lua has been loaded" )
return M
Now, in a different module — let’s just use main.lua — require the example1.lua module and see what happens (note that all code examples in this tutorial assume that the custom external modules are placed in the root level of your project folder, alongside main.lua).
-- main.lua local ex1 = require( "example1" ) ex1.testvar = "Hello World!"
As expected, upon execution of the first command, the variable ex1 becomes equal to the empty table (M) which we created and returned in example1.lua, and the words example1.lua has been loaded
In the next command, we assign a trivial property, testvar, to this ex1 table.
Pay close attention now since this gets more tricky. Let’s require example1.lua in an another module, scene1.lua, after we have already required it within main.lua:
-- scene1.lua (previous "main.lua" still applies) local examp1 = require( "example1" ) print( examp1.testvar )
This time — in scene1.lua — when you require the example1.lua module, the words example1.lua has been loadedexamp1.testvar, Hello World!testvar property exists! What can we learn from this? Read on…
The reason why example1.lua has been loadedmain.lua. Essentially, when a module is loaded (required), its code is executed from top to bottom as usual, and the return value of the module is stored in a global table called package.loaded. From that point on, the module is considered “loaded” and it can’t simply be
Let’s explore this in a bit more detail:
When you call require(), the first thing that happens is the package.loaded table is checked to see if the module was previously loaded.
If it is found, then instead of package.loaded is returned. In other words, this returned value is a reference, not a copy. Thus, if your module returns a table, that’s the same table you’ll get (properties and all) when you call require() on the same module in the future.
If the module is not found in package.loaded, the module will be loaded, the code will run from top to bottom, and the return value of the module will be stored in the package.loaded table for any future requires of the module. This explains why example1.lua has been loadedexample1.lua was already required within main.lua, it is not scene1.lua.
There are two ways to get code within a module to run:
Put the code inside a function (within the module) and call it like a normal function.
Remove the module from the package.loaded table and
Here’s an example of the first scenario:
-- example2.lua
local M = {}
print( "example2.lua has been loaded" )
M.hello = function()
print( "Hello World!" )
end
return M
-- main.lua local ex2 = require( "example2" ) --> example2.lua has been loaded ex2.hello() --> Hello World!
-- scene1.lua local examp2 = require( "example2" ) examp2.hello() --> Hello World!
As you can see, we required example2.lua in two different modules, main.lua and scene1.lua. The first print() statement — the one outside of any functions — occurs only once (the first time the module is required within main.lua). In contrast, the second print() statement — the one within the M.hello function — is executed each time that function is called.
What is the lesson learned? Essentially, if you want to run code within a module more than once, or if you don’t want to run it immediately upon requiring the module, you should wrap it within a function and “attach” the function to the table which you return at the end of the module.
Properly “attaching” module functions to the returned table is essential to gain access to them. You can not simply include the function within the module as a local function and then expect access to it from another module. Notice the difference:
local M = {}
-- This function WILL be accessible outside of this module, wherever the module is required
M.hello = function()
print( "Hello World!" )
end
-- This function will NOT be accessible outside of this module, only within it locally
local hello = function()
print( "Hello World!" )
end
return M
As outlined above, you require a module like example2.lua using the following syntax:
local ex2 = require( "example2" )
With this, the return value of example2.lua is effectively stored in the package.loaded table under:
package.loaded["example2"]
Thus, if you want to nil to this reference as follows:
package.loaded["example2"] = nil
At this point, the module will be cleared from memory and all of its associated function(s) will cease to exist.
Hopefully this tutorial has given you a solid foundational understanding of Lua modules, how to include them, and how to call functions that exist in one module from another module. Conveniently, these rules are universal and apply to any module you may be dealing with, whether it’s your own