This is somewhat a kind of misunderstood but I hope this blog post will resolve this once for all. I often saw people using Lua require
like this
local gamera = require("libs/gamera")
local nvec = require("libs/nvec")
-- Rest of the code
At first glance, there's nothing wrong right? No. Using slashes in require
works because it's an accident, but no this is not a happy accident. What you should do is:
local gamera = require("libs.gamera")
local nvec = require("libs.nvec")
-- Rest of the code
So, why the dots there? Because require
is not expecting path, it's expecting module name. To explain why, first let's take a tour to Programming in Lua Section 8.1, with important text marked as bold.
The path used by
require
is a little different from typical paths. Most programs use paths as a list of directories wherein to search for a given file. However, ANSI C (the abstract platform where Lua runs) does not have the concept of directories. Therefore, the path used byrequire
is a list of patterns, each of them specifying an alternative way to transform a virtual file name (the argument torequire
) into a real file name.
So, it's clear that require
does not expect a path to filename, but it expects module name (or virtual file name; we'll use module name from now on). To understand how Lua transform the module name to actual path that Lua will try to load, it's important to know about package.loaders
.
package.loaders
is an array of function which tries to load a module based on module name passed by require
. The Lua 5.1 manual has more information about this, but I'll try to explain it as simple as possible. In Lua 5.1 (and LuaJIT), there are 4 loaders in this entry but I'll only explain the first 3, tried in this order:
- Checks for existing module loader in
package.preload
table with the module name (passed fromrequire
) as the table key, such that when called, it loads the module. If it's non-nil, then the value is returned. - Replace all dots in module name to OS-specific directory separator (we'll call this file path). Then for each semicolon-separated path specified in
package.path
, substitute question mark with the file path then try to open that as Lua file. If it's loaded successfully then the function chunk is returned. - For this, it needs 2 components: file path and entry point. Replace all dots in module name to OS-specific directory separator to get file path, and replace all dots in module name to underscore with luaopen_ prepended to get entry point. Then for each semicolon-separated path specified in
package.cpath
(note the "c" in cpath), it tries to load said file as shared library (or DLL in Windows), then returns Lua C function with specified entry point inside the shared library. - It's all-in-one loader, doesn't matter in our case.
If you're still confused, this pseudo-Python code will help you know how it works.
import os
package.preload = dict()
package.path = "?.lua;path/to/?.lua"
package.cpath = "?.dll;?.so;path/to/?.dll;path/to/?.so"
def loader_1(modname):
module = pacakge.preload.get(modname)
if module is not None:
return module
return f"no field package.preload['{module}']"
def loader_2(modname):
file_path = modname.replace(".", os.sep)
tested = []
for path in package.path.split(";"):
file_name = path.replace("?", file_path)
chunk = load_lua_file(file_name)
if chunk is not None:
return chunk
tested.append(f"no file '{file_name}'")
return "\n".join(tested)
def loader_3(modname):
file_path = modname.replace(".", os.sep)
entry_point = "luaopen_" + modname.replace(".", "_")
tested = []
for path in package.cpath.split(";"):
file_name = path.replace("?", file_path)
module = open_shared_library(file_name) # dlopen or LoadLibraryA
if module:
symbol = get_symbol(module, entry_point) # dlsym or GetProcAddress
if symbol is not None:
return make_symbol_callable(symbol)
close_shared_library(module) # dlclose or FreeLibrary
tested.append(f"no file '{file_name}'")
return "\n".join(tested)
If that's clear enough, then stop reading and start fixing your require
by replacing slashes with dots!
"But using slashes feels more natural", you'll lose an argument if the language specifies how something behaves and you're against it unless you have very strong reason, and "feels more natural" reason is not one of them.
Consider these scenario: You have module located at "libs/mymodule.lua". Loading it as require("libs/mymodule")
works accidentally, but now consider you're replacing "libs/mymodule.lua" with "libs/mymodule.dll" or "libs/mymodule.so". Now above require
call won't work because the symbol does not exist (see loader_3
function above for why).
This is so misunderstood that I have to push a change in next major version of LÖVE to warn every users who uses slashes in their require
.
No comments:
Post a Comment