在 Lua 中,当多个脚本文件循环 require 时(例如 A 依赖 B,B 又依赖 A),最后 require 的值为 true 是由于 Lua 的 模块加载机制 和 避免无限循环 的设计导致的:
package.loaded 表跟踪已加载的模块。package.loaded 中设置 A = true(临时占位符)。require B,而 B 又尝试 require A:package.loaded[A] 已存在(值为 true)。true,避免无限循环。true)。在lua5.1中,出现循环require会直接报错(这边我们不讨论在5.1下的情况),如果报错是非常容易排查的,如下图:

假设有两个文件互相依赖:

Start loading A
Start loading B
In B, a = true <-- 循环 require 导致值为 true
In A, b = Module B <-- B 正常加载完成
In main, a = Module Amain.lua 执行 require "a",开始加载 A。require "b",开始加载 B。require "a",此时 A 正在加载中(package.loaded[a] = true),直接返回 true。a 获取到占位符 true。package.loaded["a"] 被替换为 "Module A"。a 值不会更新(仍是 true)。关键函数在 loadlib.c 中的 ll_require 函数:
static int ll_require (lua_State *L) {
const char *name = luaL_checkstring(L, 1);
// 1. 检查模块是否已加载
lua_settop(L, 1);
lua_getfield(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
if (lua_getfield(L, 2, name) != LUA_TNIL) { // 已存在
if (lua_toboolean(L, -1) == 0) // 值为 false 表示加载失败
luaL_error(L, "module '%s' not found", name);
return 1;
}
// 2. 设置临时占位符 true
lua_pushboolean(L, 1);
lua_setfield(L, 2, name); // package.loaded[name] = true
// 3. 加载模块代码...
// ...(此处调用加载器执行文件内容)
// 4. 如果模块未返回值,则保持 true 不变
if (lua_getfield(L, 2, name) == LUA_TNIL) {
lua_pushboolean(L, 1);
lua_pushvalue(L, -1);
lua_setfield(L, 2, name); // 无返回值时设为 true
}
return 1;
}package.loaded:若模块已存在,直接返回其值。package.loaded[name] = true,标记模块正在加载。require 当前模块时,直接返回占位符 true。true)。重构代码
:解耦模块间的双向依赖。
延迟加载
:在需要时再 require(例如在函数内部调用)。
-- b.lua 修复版
local a
function get_a()
if not a then a = require "a" end
return a
end显式传递依赖
:通过参数传递避免 require。
最佳实践:模块设计应遵循 单向依赖 原则,避免循环
require。若无法避免,需明确处理占位值true的情况。