Lua:200KB
1993 年生于巴西 PUC-Rio, 设计目标朴素到挑衅: 整个语言塞进 200KB, 只用 C89, 零外部依赖。33 年后 Lua 住在每个游戏引擎、每个数据库、每个编辑器里——它不抢风头, 它住在所有东西里面。
软件进口禁令的副产物
Python 嵌入式 ~50×
nil / bool / num / str / fn / userdata / thread / table
Lua 最大活跃方言
何为 Lua
Lua 是 1993 年 PUC-Rio 三人组造的嵌入式脚本语言: Roberto Ierusalimschy、Luiz Henrique de Figueiredo、Waldemar Celes。设计目标三句话讲完——"小、嵌得进 C、跑得动"。语言本身是一本 ~250 页书能讲完的工具; 它的厉害不在语言, 在它住在多少程序里。
Lua 是个 .a 静态库, 你的 C 程序 link 进去就能跑脚本。不是"Lua 调你"; 是"你嵌 Lua"。这条 1993 年的设计哲学决定了它 30 年的命运。
动态类型, 值类型仅 8 种: nil / boolean / number / string / function / userdata / thread / table。table 一个顶七个——数组、字典、对象、namespace、module 全是它。
增量 GC (5.4 起分代式), 1993 年就有的协程 (yield / resume)。无 OS 线程开销的并发原语——OpenResty 整个并发模型站在这上面。
整个解释器 ~30 个 .c 文件, 严格 ANSI C89, 不依赖 POSIX。能跑 C 的地方就能跑 Lua——这是它能进 PS3 / 路由器 / ESP32 的基础。
// C side
// link libpython3 (~30 MB)
// + libffi · + libssl · + libz · ...
Py_Initialize();
PyRun_SimpleString("print('hi')");
Py_Finalize();
// runtime: ~10-30 MB resident
// startup: hundreds of ms
// 不适合塞进游戏 frame · 路由器 · MCU// C side · 5 行就完事
// link liblua (~200 KB)
// 0 外部依赖
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_dostring(L, "print('hi')");
lua_close(L);
// runtime: ~200 KB resident
// startup: microseconds
// WoW client · Redis · Nginx · ESP32 都靠这套来路 : Timeline
33 年, 从巴西大学一间小实验室到 70M Roblox 日活——Lua 没有"明星时刻", 它是慢工出细活的样本。每一年都安静往前走一步, 然后某天发现它已经住在你每天用的所有东西里。
- 1993·07
诞生于 PUC-Rio
巴西里约热内卢的天主教大学 (PUC-Rio), Roberto Ierusalimschy、Luiz Henrique de Figueiredo、Waldemar Celes 三人组在 Tecgraf 实验室造出 Lua 1.0。背景: 80 年代巴西禁止进口商业软件, 高校自己写工具——Lua 是这条限制的副产品。moon in Portuguese。
- 1995
Lua 2.0 — 设计哲学定型
"language as a library" 这条核心定下来: Lua 不试图统治你的程序, 它是你 C 程序里的一个 .a 文件。配置文件、脚本钩子、内嵌求值——这些用例从此长在 Lua 的基因里。
- 1996·12
Dr. Dobb's 文章 — 走出巴西
Ierusalimschy 等人在 Dr. Dobb's Journal 发文介绍 Lua, 英语圈第一次知道这门小语言。游戏开发者很快读到——这是 LucasArts / Bioware 等把 Lua 嵌进引擎的种子时刻。
- 2003
Lua 5.0 — 现代 Lua 的起点
引入词法作用域 closure、coroutine、真函数式 metatable。这一版之后语言"成形"了——之后 20 年的核心几乎不动, 改的是边角。许多老的 Lua 教材其实在描述 5.0。
- 2004·11
World of Warcraft 上线 — Lua 进千万人客户端
Blizzard 把 Lua 嵌进 WoW 客户端, 整个 UI + addon 系统用 Lua 写。WoW addon 一夜让 Lua 成为世界上间接用户最多的脚本语言之一: 几千万玩家 + 几万开发者。
- 2005·05
LuaJIT 上线 — Mike Pall 一人作品
奥地利程序员 Mike Pall 发布 LuaJIT 1.0: 给 Lua 加 trace-based JIT, 性能跨数量级提升。后续 LuaJIT 2 在数值循环上能跑赢 V8 / JVM——这在脚本语言里是异常稀有的。Lua 5.1 兼容这条线由此锁死。
- 2006
Lua 5.1 — 长期之锚
5.1 看起来只是版本号 +0.1, 实际成了 Lua 史上影响最大的一个版本。LuaJIT 永远 5.1 兼容; OpenResty / Redis / 游戏引擎 / Roblox Luau 都钉在 5.1 ABI 上。5.1 不是"老版本", 是事实标准。
- 2008
Adobe Lightroom — Lua 写桌面 app
Adobe 公开 Lightroom 的 UI 层大部分是 Lua 写的 (Lightroom SDK 也是 Lua)。这是"Lua 不只是游戏脚本"最有力的早期证据——专业桌面软件能整面 ship。
- 2011
Lua 5.2 — 分叉的起点
5.2 改
setfenv、加goto、改__pairs。社区分裂: LuaJIT 留 5.1, 主线走 5.2。从这刻起 "Lua 5.x" 不再是一个单一标签——选哪条线决定你的库生态。 - 2011·11
OpenResty 1.0 — agentzh 把 Nginx 变成 Lua 平台
章亦春 (agentzh) 发布 OpenResty: Nginx + LuaJIT, 在 worker 里跑 Lua 协程。Cloudflare 边缘多年跑这一套; 国内淘宝 / B 站 / 知乎 早期网关也是。Lua 第一次进"互联网基础设施"层。
- 2012·06
Redis 2.6 —
EVAL让 Lua 跑进世界最大缓存antirez (Salvatore Sanfilippo) 在 Redis 2.6 加
EVAL, Lua 成了 Redis 内嵌的脚本语言。原子化复合操作的口子从此打开——亿万 web 后端默默调过redis.call。Lua 选中的原因? 嵌入轻、零依赖、行为确定。 - 2015
Lua 5.3 — 整数类型上线
5.3 把整数加回来 (此前 Lua 只有 double)。
//、位运算& | ~ <<、字符串打包。重要但 LuaJIT 没跟——5.1 vs 5.3 的鸿沟拉得更深。 - 2015
Mike Pall 退场公告
LuaJIT 作者 Mike Pall 公开宣布退出主导开发, 寻找接班人。社区震动: 一个能跟 V8 比性能的项目, 从头到尾几乎都是他一个人写的。后续 fork (luajit2 OpenResty 维护、moonjit 等) 接力, 主线 LuaJIT 至今仍由社区维护更新。
- 2017
Defold / Love2D 巩固独立游戏阵地
King 把内部引擎 Defold 开源 (Lua 脚本); Love2D 持续被独立游戏开发者使用。加上 Garry's Mod + Factorio mod, "Lua = 游戏脚本事实标准"被进一步钉死。
- 2019·02
Roblox 公开 Luau — Lua 的 typed fork
Roblox 把内部 Lua fork 改名 Luau 并开源: 加渐进类型、加沙盒、移除
loadstring等危险操作。Roblox 日活 ~7000 万玩家, 数百万开发者——Luau 是世界上活跃用户数最大的 Lua 方言。 - 2019·11
Neovim 0.4 — Lua 成一等公民
Neovim (Vim 的 2014 fork) 在 0.4 放出 嵌入 Lua 5.1。Bram Moolenaar 都承认过 "VimL 设计糟"; Neovim 团队用 LuaJIT 把这页翻过去。从此 nvim 配置 / 插件首选 Lua, VimL → Lua 的迁移浪潮启动。
- 2020·06
Lua 5.4 — to-be-closed 变量
5.4 加
<close>属性 (RAII 风作用域结束自动 close)、改 GC 为分代式。语言层第一次有"资源管理"语义。但 LuaJIT 仍然只跟 5.1——分裂继续。 - 2020·07
Neovim 0.5 + lua-first 插件浪潮
0.5 放出完整
vim.apiLua 表面, telescope.nvim / nvim-treesitter / lazy.nvim 这一批 Lua-only 插件冒出来。两年内 Neovim 插件主流变成纯 Lua。 - 2022·04
Redis 7.0 — Functions 上线, EVAL 被边缘化讨论
Redis 加 Functions (持久化 Lua 函数), 一度有讨论"EVAL 何时弃用"。社区反弹强烈——Lua 早已嵌进无数生产代码。最终 Redis 7.4 把 Lua 重新定位为一等公民, 跟 Functions 并存。
- 2023·05
Lua 5.4.6 + LuaJIT 2.1 final release candidates
主线 Lua 进入 "小修小补" 稳态; LuaJIT 2.1 终于打出正式 RC, 性能微调。Lua 在 2023 年是"成熟到边边都收拾完"的状态——这是嵌入式语言最幸福的形态。
- 2026
33 岁 — 在数千万嵌入位上跑着
2026 年的 Lua 现状: 主线 5.4 稳定; LuaJIT 2.1 由社区 + OpenResty fork 维护; Luau 是最活跃方言 (Roblox 数千万日活); Neovim 是 Lua 的新身份门面; Redis / OpenResty / WoW / Factorio / Lightroom 老阵地不动。Lua 不抢风头, 它住在每个东西里面。
语言精要 : LuaAlphabet
8 张卡讲 Lua 跟其它语言最不一样的地方: table 当一切、metatable 当根、coroutine、closure、朴素 OOP、require、8 个类型、C ABI。第 9 张是"Python 超集"的反面: Lua 不试图替代 Python, 它选了完全相反的赛道。
table — 唯一的数据结构
Lua 只有一种复合类型: table。它同时是数组、字典、对象、namespace、module。把全部精力压在一个数据结构上, 优化得很扎实。
local t = {
-- array part
"hello", "world",
-- hash part
name = "lua",
year = 1993,
}
print(t[1], t.name)metatable — 一切高级抽象的根
OOP、运算符重载、读写代理、继承——全部由 metatable 一套机制实现。__index、__newindex、__add 等元方法是 Lua 的底层魔法。
local Vec = {}
Vec.__index = Vec
Vec.__add = function(a,b)
return setmetatable({a.x+b.x}, Vec)
endcoroutine — 协程一等公民
1993 年就有协程的脚本语言。coroutine.create / yield / resume 三件套, 无 OS 线程开销。OpenResty 的并发模型核心就是 Lua 协程。
local co = coroutine.create(function()
for i=1,3 do
coroutine.yield(i)
end
end)
print(coroutine.resume(co)) -- true 1closure — 词法捕获 + upvalue
从 5.0 起 Lua 是真正的词法作用域语言, closure 捕获 upvalue。函数是一等值, 可塞 table、可作返回、可当参数。函数式风的工具基本都齐。
local function counter()
local n = 0
return function()
n = n + 1
return n
end
endOOP — 朴素到优雅
Lua 没有 class 关键字。OOP = table + metatable + : 语法糖 (self 注入)。20 行就能教会一个新手 OOP 怎么"长出来"。各引擎各自约定一种, 没有标准, 也不需要标准。
local Dog = {}
Dog.__index = Dog
function Dog.new(name)
return setmetatable({name=name}, Dog)
end
function Dog:bark() print(self.name) endrequire + package.path
模块系统极简: require "foo" 沿 package.path 查 ?.lua, 加载、缓存、返回。没有 npm-级别复杂度。LuaRocks 是社区事实包管理但不强制——很多项目仍 vendor 整个文件。
-- foo.lua
local M = {}
function M.hello() return "hi" end
return M
-- main.lua
local foo = require "foo"类型: 仅 8 个
nil / boolean / number / string / function / userdata / thread / table——就这 8 个。整个语言能在两页纸讲完。Luau 在此基础上加了渐进类型, 但核心 Lua 保持纯净。
print(type(nil)) -- nil
print(type(true)) -- boolean
print(type(1)) -- number
print(type({})) -- table
print(type(print)) -- functionC ABI — 嵌入易如反掌
Lua 是为嵌进 C 程序设计。lua_State*、lua_push* / lua_to* 栈机, 整个 API 极简且稳定——这是 WoW / Redis / Nginx / Lightroom 都选 Lua 的核心原因。
// C side
lua_State *L = luaL_newstate();
luaL_openlibs(L);
luaL_dostring(L, "print('hi')");
lua_close(L);反 Python 哲学 — 故意不长大
Python 30 年膨胀到 stdlib 上千万行、关键字几十个、PEP 几百条。Lua 30 年? 关键字仍 22 个, 核心 stdlib 仍 ~200 KB。Roberto 多次明确拒绝把语言"现代化": 不加类, 不加类型, 不加 async/await。这是 BDFL 主动选择不长大——保住嵌入位才是身份。
"把 Lua 加到不像 Lua 的样子, 我宁可不加。" — Roberto Ierusalimschy, paraphrased from talks
为何要用 : WhyLua
选 Lua 跟选 Python / Node 的决策维度不一样。Python 比生态; Lua 比"能不能塞进我的 C 程序"。这块没有第二选——Lua 在嵌入位上事实垄断 30 年。
200 KB — 整个解释器装一只小包里
Lua 5.4 编译后核心约 200 KB (Python 3 嵌入式构建 ~10-30 MB, Node ~50+ MB)。不是"轻量级"修辞——是真的小到可以塞硬件配置面板里。这是它能进 WoW 客户端、嵌入式设备、Redis worker 的第一前提。
-- lua-5.4.6 source
-- ~30 .c files
-- 0 external deps
-- builds on ANY C89 compiler零依赖 — 任何 C 编译器都能编
Lua 严格用 ANSI C89, 不依赖 POSIX、不依赖 libc 高级功能。从 mainframe 到嵌入式 RTOS, 只要能跑 C 就能跑 Lua。这是"language as a library"的硬条件。
// embed in 3 lines
// no autoconf · no cmake
// gcc *.c -o lua嵌入哲学 — 你嵌 Lua, 不是 Lua 嵌你
Python / Node 的设计是"我管运行时, 你写脚本"; Lua 反过来——你的 C 程序是宿主, Lua 进来给你扩展力。这条哲学决定了 30 年间它住在每个游戏引擎、每个数据库、每个编辑器里面。
// 你的 C app
// ├── 业务逻辑 (C)
// └── lua_State (脚本钩子)LuaJIT — 一人作品 vs V8
Mike Pall 写的 LuaJIT 在数值循环上能跑赢 V8 / JVM——脚本语言里这是异常稀有现象。trace-based JIT + 极简 IR + 死磕底层。一个奥地利人, 几年, 把 JS 工业军团比下去。
-- LuaJIT 2.1 (5.1 compat)
-- trace JIT · loop hot-path
-- C FFI for zero-cost interop"5.1 永生" — 兼容性的祝福与诅咒
LuaJIT 钉 5.1, 整个嵌入式 / 网关 / 游戏圈跟着钉 5.1。10 年前的脚本现在还能跑——这是祝福。但 5.3 整数、5.4 RAII 在这条线上永远拿不到——这是诅咒。Lua 社区两线并行已成事实, 不再期待统一。
-- 5.1 line · OpenResty · WoW · Redis
-- 5.4 line · neovim host · mainline embed
-- both alive · both maintained谁在用 : EmbeddedHallOfFame
Lua 的名单不夸张: 它确实住在你每天用的所有东西里。从 Roblox 到 WoW、Redis 到 Nginx、Neovim 到 Lightroom——嵌入式语言的名人堂。每一个都是真用户, 用了多年, 没在炒作。
对比 : Lua vs Python vs JS
跟 Python 比: Lua 是 Python 的反面——故意小, 故意 niche。跟 JS 比: 都是 1990 年代生的动态脚本, 但 JS 把生态压到浏览器, Lua 压到嵌入。跟 C 比: Lua 的家就是 C, 共生关系不是替代关系。
| Python | Lua | JavaScript | |
|---|---|---|---|
| 出身 | Guido · 1991 | PUC-Rio · 1993 | Brendan Eich · 1995 |
| 设计师 | Guido van Rossum | R. Ierusalimschy + 2 | Brendan Eich |
| 设计目标 | 可读 · 通用脚本 | 嵌进 C · 小 · 可移植 | 浏览器表单胶水 |
| 核心大小 | 嵌入式 ~10-30 MB | ~200 KB | V8 ~50+ MB · QuickJS ~1 MB |
| 关键字数 | ~35 | 22 | ~60 |
| 数据结构 | list / dict / tuple / set / ... | table 一个顶七个 | Array / Object / Map / Set / ... |
| OOP | class 关键字 | metatable 拼出来 · 无关键字 | class (ES6) + prototype |
| 协程 | asyncio (后加) · generators | 1993 起一等公民 | async/await (ES2017) |
| 性能 (typed loop) | CPython 慢 · PyPy 中等 | LuaJIT 跟 V8 同档 | V8 · 最强 JIT 之一 |
| 嵌入易度 | 难 · libpython 巨大 | 5 行 C 搞定 · 200 KB | QuickJS 可以 · V8 不可能 |
| 生态规模 | PyPI ~50 万包 | LuaRocks ~5000 · vendor 居多 | npm ~250 万包 |
| 典型场景 | 数据 · ML · web · 脚本 | 游戏脚本 · 配置 · 嵌入 | 浏览器 · Node · 全栈 |
前景 : TheRoadAhead
Lua 2026 年的核心问题不是"怎么发展", 是"谁能撼动嵌入位"。30 年没有真威胁。主线 5.4 稳定, LuaJIT 接班悬而未决, Luau 接管类型化需求, Neovim 把它推上新一代开发者桌面。这是一门已经赢过的语言, 在守自己赢来的地。
Luau 的崛起 — 类型化 Lua 是不是未来?
Roblox 的 Luau 给 Lua 加渐进类型、加沙盒、移除危险操作 (loadstring、setfenv)。它不是分裂主线——而是给"需要类型的 Lua 用户" 一个完整答案。日活规模 ~7000 万, 数百万开发者在写。
意义: Lua 主线一直回避加类型 (Roberto 明确表态)。Luau 让"想要类型的人"另立一支, 主线保持纯净。这是 BDFL 语言设计的成熟形态: 不"all things to all people", 让方言去满足子需求。
Neovim — Lua 的"新身份"
过去 30 年 Lua 的大众形象都是"游戏引擎里的脚本"。2020 年后 Neovim 把它推上开发者工具桌面——~/.config/nvim/init.lua 已经是数百万开发者每天写的文件。这是 Lua 身份的扩展: 从游戏圈走到编辑器圈。
LuaJIT 的接班问题
Mike Pall 2015 年退后, LuaJIT 没有出现真正的"下一个 Mike Pall"。社区维护稳定但大改革停摆。OpenResty 的 luajit2 fork 补一些, moonjit 试过更激进的改, 主线仍是事实标准。能不能在 GPU / WASM 等新目标上跟上, 是开放问题。
嵌入位仍在长 — IoT / 边缘
Lua 的200 KB + 零依赖属性在 2026 年比 1996 年更值钱: ESP32 / RP2040 等小芯片直接跑 Lua, 路由器 / 工业控制器把 Lua 作配置层。"language as a library" 是个永不过时的市场——而 Lua 在这块没有真正的对手 (MicroPython 体积大数倍)。