Page 1 of 1

Subtle issue with Lua require

Posted: Sun Sep 28, 2014 5:12 am
by hardcrawler
Just a random Lua tip. This is something I didn't know, and it cost me a few hours.

Code: Select all

local foo = require("libs.foo")
local foo2 = require("libs.foo")
if foo2 ~= foo then
        print("This will not happen")
end

local foo3 = require("libs/foo")

if foo3 ~= foo then
        print("Oh my.  Using a slash instead of a dot is broken.")
end
When you use require, Lua keeps track of the things you have required previously. If you require the same library multiple times, it is only executed once.

Lua keeps track of this in a table somewhere. In other news, Lua supports multiple syntaxes for require. One of these syntaxes allows a "/" to denote directories. One of the other syntactic behaviors allows a "." to denote directories.

Long story short, if you mix and match the "." syntax with the "/" syntax, require will execute the required script multiple times.

This cost me 3 hours. Remember kids: Stay consistent with your require syntax!

Re: Subtle issue with Lua require

Posted: Sun Sep 28, 2014 7:36 am
by Jasoco
Is there any reason you can't just use love.filesystem.load(file)() to execute a file instead when you need to load it multiple times?

Re: Subtle issue with Lua require

Posted: Sun Sep 28, 2014 8:48 am
by bartbes
hardcrawler wrote:In other news, Lua supports multiple syntaxes for require.
It doesn't. It just doesn't prevent you from using the wrong one (which is the slash).
hardcrawler wrote: Long story short, if you mix and match the "." syntax with the "/" syntax, require will execute the required script multiple times.
Of course, all this works with module names, if you give it another module name (an incorrect one at that), it considers it a different module.

Re: Subtle issue with Lua require

Posted: Sun Sep 28, 2014 1:43 pm
by hardcrawler
bartbes wrote: Of course, all this works with module names, if you give it another module name (an incorrect one at that), it considers it a different module.
Bear in mind that the Lua loads the module in both cases. It doesn't generate an error. I use the "." syntax everywhere, I just happened to mess it up in one case, and it didn't generate an error, but loaded two copies of the module.

I consider this to be a pretty subtle and interesting behavior, worthy of note.

Re: Subtle issue with Lua require

Posted: Sun Sep 28, 2014 1:53 pm
by hardcrawler
Jasoco wrote:Is there any reason you can't just use love.filesystem.load(file)() to execute a file instead when you need to load it multiple times?
One of the features of require is that it will only perform file I/O and parsing on a particular module once. Subsequent uses of "require" will simply return the table resulting from the last call to require for that module. (It was the violation of this behavior that sparked my initial post).

love.filesystem.load does not have this advantage. It will re-parse and interpret the file on every call.

Code: Select all

        local chunk = love.filesystem.load( "libs/foo.lua" )
        local chunk2 = love.filesystem.load( "libs/foo.lua" )
        assert(chunk == chunk2, "Nope, they aren't the same.")

Re: Subtle issue with Lua require

Posted: Tue Sep 30, 2014 3:40 am
by paulclinger
hardcrawler wrote:Bear in mind that the Lua loads the module in both cases. It doesn't generate an error. I use the "." syntax everywhere, I just happened to mess it up in one case, and it didn't generate an error, but loaded two copies of the module.
Right, there is no error. You just used a different way to load a module. There is no mystery in how it' handled: after a module is loaded successfully, its result is saved in `package.loaded[name]` and for each `require`, Lua checks if the value is already present in `package.loaded` table and returns that value. So, in your case, one call to require created `package.loaded['libs.foo']` and the other call created `package.loaded['libs/foo']`; both were executed successfully and stored as two different results. This also means that you can control "require" behavior by setting `package.loaded[module]` to nil to force require to reload the package again.