Lua 是什么:一门装进别人程序里的语言
Lua 诞生于 1993 年,在葡萄牙语里是“月亮”的意思。它很少当主角,更多时候是被嵌进用 C 或 C++ 写成的宿主程序里,负责那些需要频繁改动、又不想重新编译整个软件的逻辑。三十多年过去,《魔兽世界》的插件、Redis 的脚本、Nginx 网关的扩展里,依然跑着它。
名字、出身与设计哲学
Lua 出自巴西里约热内卢天主教大学(PUC-Rio)的 Tecgraf 实验室,作者是 Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo 三个人。它最初为巴西石油公司的数据描述与配置需求而写,从诞生那天起就没打算做大型独立应用,而是准备被“嵌”进别人的程序。
它的设计原则通常被概括成“机制而非策略”。语言只给一套足够小的底层零件,怎么组合由你自己定。所以 Lua 里没有 class,没有继承关键字,也没有 try/catch,但用表、元表和协程,你照样能把这些拼出来。整个官方实现是两万多行标准 C,跨平台能力接近一句老话:能跑 C 的地方就能跑 Lua。
它凭什么被反复选择
- 体积小。解释器核心只有几百 KB,编译出的可执行文件常在 200 KB 上下,塞进路由器和单片机都没什么负担。
- 跑得快。虚拟机按寄存器模型设计,纯计算任务通常比 Python 快,换成 LuaJIT 之后差距还会拉开。
- 好嵌入。官方只提供一套精简的 C API,宿主程序愿意开放哪些能力,完全由自己决定。
- 语言小。关键字二十来个,语法几天就能上手,但表和元表足以撑起复杂系统。
- 活得久。从 1993 年延续至今,语法改动一直很克制,老代码的迁移成本相对可控。
Lua 5.5:2025 年底的这次大更新
先看一张现状表,把版本和生态的坐标定下来。
| 项目 | 现状 |
|---|---|
| 标准 Lua | 5.5.0,2025 年 12 月 22 日发布 |
| 当前补丁 | 5.5.1,2026 年 8 月 3 日发布 |
| 上一代稳定版 | 5.4 系列,补丁已更新到 5.4.9 |
| 高性能实现 | LuaJIT,走 2.1 滚动更新,兼容 Lua 5.1 |
| 包管理 | LuaRocks |
| 语言服务器 | lua-language-server |
从 2020 年 6 月的 5.4.0 到 5.5.0,中间隔了大约五年半,这也是 Lua 少见的一次大版本跨越。它没有往语言里堆新概念,改动集中在内存、延迟和安全性这三处长期被抱怨的地方。
数组更紧凑,内存省得多
Lua 只有表这一种容器,数组也是用表模拟出来的。过去纯整数下标的表走通用结构,元素一多内存就堆得厉害。5.5 换了一套更紧凑的数组布局,官方在变更说明里给出的数字是:大型数组的内存占用最多可以减少约 60%。对嵌入式设备和手游这类内存吃紧的场景,升级后不用改一行代码就能拿到这份收益。
垃圾回收改成增量式
5.4 主打的是分代回收,思路是“大多数对象死得早”,先频繁清理年轻对象。5.5 把主回收周期改成了增量执行:一次大的回收不再一口气暂停几十毫秒,而是拆成小步,插在程序运行中间完成。对帧循环、实时音视频这类对延迟敏感的负载,这项改动的意义比省内存还大。
全局变量要显式声明
这条最可能影响老代码。5.5 把 global 收作保留字,变量默认不再自动变成全局,想用全局就得明确声明。好处是拼错变量名、漏写 local 这类低级错误能在早期就被抓出来;代价是升级前得把散落各处的隐式全局梳理一遍。
-- Lua 5.5 新特性一:显式声明全局变量
global AppName = "demo" -- 声明一个全局变量
global Port <const> = 8080 -- 全局常量
-- 只要一个块里出现过 global 声明,这个块里所有变量都必须先声明
-- EnableDebug = true -- 这里会直接报错,而不是静默创建全局变量global 还有通配形式 global *,表示“其余未声明的名字都当全局”,专门用来给老代码过渡。
其它调整与升级注意
还有一批零碎但实用的改动:for 循环里的控制变量变成只读,想改就得在循环体内另声明一个同名局部变量;浮点数默认以足够的十进制精度输出,能被原样读回;新增 table.create 用于高效建表;utf8.offset 现在还会返回字符的结束位置;支持由外部内存管理的字符串,方便和 C 侧做零拷贝交互。
不兼容的地方集中在几处:global 成了保留字;__call 元方法链最多只能串 15 个对象;错误处理里 nil 作为错误对象会被替换成字符串;C API 侧 lua_resetthread、lua_setcstacklimit 等函数被弃用,垃圾回收参数改用统一的 param 选项传递。完整的变更清单以官方文档为准:Lua 5.5 参考手册。
十分钟把 Lua 跑起来
安装
Linux 上能直接用包管理器装:
sudo apt install lua5.4 # Debian 或 Ubuntu,装 5.4
brew install lua # macOS想用最新的 5.5,从官方源码编译最省心:
curl -R -O https://www.lua.org/ftp/lua-5.5.0.tar.gz
tar zxf lua-5.5.0.tar.gz
cd lua-5.5.0
make linux # 其他平台把 linux 换成对应目标
make test # 跑一遍自带测试
sudo make install # 安装到系统目录编译完成后,src 目录下会得到三个东西:解释器 lua、编译器 luac,以及静态库 liblua.a。
第一个脚本
新建一个 hello.lua,写入下面的内容:
-- 字符串用两个点号拼接
local name = "世界"
print("你好," .. name)
-- 表既能当数组也能当字典
local person = { name = "阿宝", age = 30, skills = { "Lua", "C" } }
print(person.name, person.skills[1]) -- 索引从 1 开始
-- 遍历序列用 ipairs,遍历键值对用 pairs
for i, skill in ipairs(person.skills) do
print(i, skill)
end存盘后执行 lua hello.lua 就能看到输出。
交互式解释器
直接敲 lua 会进入 REPL,写一句执行一句,用来试语法特别顺手。
lua
> print(1 + 2)
3
> =math.pi
3.14159265358985.5 里 REPL 还支持动态加载 readline,补全和历史记录比过去顺手了一些。
核心语法速通
八种基本类型
Lua 的类型很少,一共八种:nil、boolean、number、string、function、thread、table、userdata。数字在 5.3 之后区分整数和浮点两个子类型,math.type(3) 返回 integer,math.type(3.0) 返回 float。
变量不需要声明类型,赋什么就是什么。默认作用域是全局,所以写代码时要养成加 local 的习惯,性能和变量污染两方面都受益。
表:唯一却万能
表是 Lua 里唯一的内置容器,数组、字典、对象、模块全靠它。索引默认从 1 开始,没赋值的空位是 nil,# 用来取长度。
local arr = { "a", "b", "c" }
print(#arr) -- 3
local dict = { x = 1, y = 2 }
dict.z = 3 -- 字段随时能加
print(dict.z) -- 3控制结构与函数
-- if / elseif / else
local score = 85
if score >= 90 then
print("优秀")
elseif score >= 60 then
print("及格")
else
print("不及格")
end
-- 数值 for
for i = 1, 5 do print(i) end
-- while
local n = 3
while n > 0 do
print(n)
n = n - 1
end
-- 函数是一等公民,可以赋值、传参、被返回
local function add(a, b) return a + b end
print(add(2, 3))闭包是 Lua 用得最多的一招,函数会把定义时所在的局部变量一起带走。
local function make_counter()
local count = 0
return function()
count = count + 1
return count
end
end
local next_id = make_counter()
print(next_id()) -- 1
print(next_id()) -- 2元表与面向对象
Lua 没有 class,靠元表的 __index 元方法实现继承,这是 Lua 写面向对象的标准套路。
local Animal = {}
Animal.__index = Animal
function Animal.new(name)
local self = setmetatable({}, Animal)
self.name = name
return self
end
function Animal:speak()
print(self.name .. " 叫了一声")
end
-- 让 Dog 继承 Animal
local Dog = setmetatable({}, { __index = Animal })
Dog.__index = Dog
function Dog.new(name)
local self = Animal.new(name)
return setmetatable(self, Dog)
end
function Dog:speak()
print(self.name .. " 汪了一声")
end
Dog.new("旺财"):speak() -- 旺财 汪了一声同样的机制还有一圈兄弟姐妹:__newindex 拦截对不存在的键的赋值(只读表、代理表都靠它)、__add 与 __eq 等运算符元方法让表能参与加减比较、__len 接管长度操作、__call 让表能像函数一样被调用。这些机制拼起来,Lua 就有了面向对象、面向切面乃至各种花式玩法——代价是完全没有编译期检查,字段写错只能等运行时发现。
协程与错误处理
协程是“能随时暂停和恢复的函数”,是 Lua 里做生成器、状态机和协作式并发的原语。它不是线程,没有抢占,切换点完全由代码里的 yield 决定:
-- 协程:每次 resume 推进一步,yield 交出控制权
local producer = coroutine.create(function()
for i = 1, 3 do
coroutine.yield(i * i)
end
end)
print(coroutine.resume(producer)) -- true 和 1
print(coroutine.resume(producer)) -- true 和 4
print(coroutine.resume(producer)) -- true 和 9
print(coroutine.resume(producer)) -- true,协程已跑完,没有值了resume 的第一个返回值永远是“是否成功”,出错时第二个返回值是错误对象。这也引出了 Lua 的错误处理约定:库函数失败返回 nil 加错误消息,业务代码用 error 抛错、pcall 兜底:
-- 错误处理:pcall 兜底,xpcall 能带出完整调用栈
local function charge(amount)
if amount <= 0 then
error("充值金额必须大于 0", 0) -- 第二个参数 0 表示错误消息不带位置前缀
end
return amount
end
local ok, result = pcall(charge, -5)
print(ok, result) -- false 和 充值金额必须大于 0
-- 想拿到出错时的调用栈,把 debug.traceback 作为处理器传给 xpcall
local ok2, err2 = xpcall(charge, debug.traceback, -5)版本进化史:从 5.0 到 5.5 每代加了什么
把主版本按时间铺开,能看清 Lua 的升级节奏——稳,但每一步都动真格:
| 版本 | 发布年份 | 里程碑 |
|---|---|---|
| Lua 1.0 | 1993 | 首个版本,诞生于 PUC-Rio |
| Lua 5.0 | 2003 | 元表机制与协程成型 |
| Lua 5.1 | 2006 | module 与 require 模块体系 |
| Lua 5.2 | 2011 | _ENV 环境机制、goto 语句 |
| Lua 5.3 | 2015 | 独立整数类型、位运算、utf8 库 |
| Lua 5.4 | 2020 | 分代垃圾回收、const 与 close 属性 |
| Lua 5.5 | 2025 | 全局变量声明、命名可变参数、只读循环变量 |
5.3:整数从浮点里分家
5.3 之前 Lua 只有一种“数”,全是双精度浮点。5.3 把 64 位整数请回语言,除法和整除从此是两回事,位运算也进了语言:
-- Lua 5.3:整数除法与位运算
print(7 / 2) -- 3.5,普通除法结果是浮点数
print(7 // 2) -- 3,整除
print(1 << 10) -- 1024,位移
print(3 & 5) -- 1,按位与5.4:常量、自动关闭与分代回收
5.4 给局部变量加了两个属性:const 属性声明常量,赋值直接在编译期报错;close 属性声明“离开作用域自动关闭”的变量,配合元方法 __close,文件句柄、锁这类资源再也不用手动清理:
-- Lua 5.4:to-be-closed 变量,函数怎么退出都会自动关门
local function read_config(path)
local f <close> = assert(io.open(path, "r"))
return f:read("a") -- 无论正常返回还是中途出错,f 都会被自动关闭
end
local rate <const> = 0.5
-- rate = 0.6 -- 编译期直接报错同版本把垃圾回收升级为分代模式,并新增 warn 函数和整型 for 循环的默认步长修正。
LuaJIT 与 Luau:绕不开的两条岔路
标准 Lua 之外有两套实现必须认识。LuaJIT 是 Mike Pall 自 2005 年起维护的即时编译实现,官网明确以 Lua 5.1 语义为基础,附带 JIT 编译、BitOp 位运算和 FFI 外部函数接口——FFI 让你在 Lua 里直接声明 C 函数和结构体来调用,不再需要写胶水绑定,Neovim 和 OpenResty 都跑在它上面。它的 2.1 分支以滚动发布方式长期演进,不追随官方 5.4 与 5.5 的语言特性。
Luau 则是 Roblox 为自家游戏平台从 Lua 5.1 出发做的开源方言,加入了渐进类型注解、严格模式等工程化特性。也就是说,2026 年同时存在“官方 Lua(5.4 与 5.5)”“LuaJIT(5.1 语义加 JIT)”“Luau(5.1 语义加类型系统)”三条线。给宿主写代码时认准宿主用哪套,别把 5.5 的 global 声明写进 Neovim 配置里——LuaJIT 根本不认识它。
从入门到精通的学习路线
“精通”这个词有点大,落到实处是一层一层往上垒。
| 阶段 | 目标 | 建议投入 |
|---|---|---|
| 入门 | 装好解释器,跑通值、表、控制流、函数 | 一到两周 |
| 核心进阶 | 吃透元表、协程、错误处理,能读懂别人的插件 | 三到四周 |
| 工程化 | LuaRocks 装库、luacheck 查错、LuaLS 补全、打包自己的库 | 一到两个月 |
| 精通 | 用 C API 把 Lua 嵌进自己的项目,精读官方源码,跟进 lua-l | 长期 |
第一阶段:把语法和标准库用熟
目标是不用查手册也能把语言写顺。重点是表的构造与遍历、多返回值、可变长参数、尾调用、字符串模式匹配(不是正则,是 Lua 自己的一套),以及和 io、os、math、string、table 这几个标准库打交道。这一阶段解决的是“能写出来”。
第二阶段:学会工程化
语法会了,还得知道怎么组织代码。用 require 拆模块,用 LuaRocks 管依赖,让每个模块把要暴露的东西 return 出去,再给项目配一个 .luarc.json,让语言服务器认得你的目录结构。这一阶段解决的是“能维护”。
第三阶段:碰 C 与性能
真正拉开差距的是这几块:元表的高级用法、协程在业务里的调度、C API 与宿主程序的双向调用,以及定位性能瓶颈。写游戏或网关的人,最后基本都会被推到这一步。用 collectgarbage("count") 看内存,用 LuaJIT 顶住热点路径,把重活下放到 C 侧,都是常见套路。这一阶段解决的是“能扛住”。
Lua 现在都在哪干活
宿主全景
学 Lua 的第一课是认清定位:Lua 几乎从不单独作为主语言去开发完整应用,它的主战场是当宿主程序里的嵌入脚本层。下面这张表能把生态定位一眼看清。
| 宿主 | 用 Lua 做什么 | 用的哪套实现 |
|---|---|---|
| Neovim | 配置文件与全部插件生态 | LuaJIT(Lua 5.1 语义) |
| OpenResty | Nginx 上的动态网关与业务逻辑 | LuaJIT |
| Redis | EVAL 服务器端脚本 | 官方 Lua 5.1 |
| Roblox | 全部游戏脚本 | Luau 方言 |
| 魔兽世界 | 插件开发 | 官方 Lua 5.1 |
| LÖVE(Love2D) | 2D 独立游戏开发 | 官方 Lua |
| Defold | 2D 与 3D 游戏脚本 | 官方 Lua |
| Nmap 与 Wireshark | 扩展脚本 | 官方 Lua |
2026 年学 Lua 最现实的三个理由,恰好对应三个方向:给 Neovim 写配置和插件(编辑器方向)、用 LÖVE 或 Defold 做独立游戏(游戏方向)、在 OpenResty 网关层写业务(服务端方向)。
游戏与插件
游戏是 Lua 最大的地盘。《魔兽世界》的界面插件、《愤怒的小鸟》的游戏逻辑、Roblox 平台上的创作脚本,用的都是 Lua 或它的方言 Luau。引擎把渲染、物理这些重活留在 C++ 里,把玩法、任务、UI 交给 Lua,改内容不用重编客户端,热更新一下就行。
网关与 Web
OpenResty 把 Nginx 和 LuaJIT 拼在一起,用 Lua 写网关逻辑、限流、鉴权、动态路由,是国内后端很常见的一套组合。对熟悉 Nginx 配置、又想给请求处理加一点编程能力的人来说,这条路的上手成本很低。
嵌入式与物联网
解释器小、能裁剪、能嵌 C,这些特点让 Lua 在嵌入式里站得很稳。路由器固件 OpenWRT 用 Lua 做配置,NodeMCU 一类物联网固件直接用 Lua 控制硬件,一些工业控制器也让用户用 Lua 写简单的联动规则。
数据库与配置
Redis 支持执行 Lua 脚本,可以把你的一串命令打包成一次原子操作,避免来回的网络开销。也有不少软件干脆拿 Lua 当配置文件格式,因为它既可读又能执行,比 JSON、XML 灵活得多。
工具链与学习资料
运行时与包管理
标准 Lua 从官方网站获取,源码和手册都在那里。需要更高性能时用 LuaJIT,它兼容 Lua 5.1 语法,还附带 FFI 可以直接调 C 函数。装第三方库用 LuaRocks,用法和 pip、npm 差不多。
编辑器与语言服务器
写 Lua 建议配上 lua-language-server。它本身也是用 Lua 写成的,支持从 5.1 到 5.5 以及 LuaJIT,能补全、能查错、能跨版本做兼容性提示,接进 VSCode 或 Neovim 都行。再把 luacheck 挂到编辑器上,未定义变量、全局污染这类问题当场就能标出来。
书与官方文档
最权威的入门书是 Lua 作者亲自写的《Lua 程序设计》第 4 版,中文版由电子工业出版社引进,英文原版 Programming in Lua 在官网可查。这本书的内容基于 Lua 5.3,语言思想部分完全通用,但讲到垃圾回收、整数除法这些细节时,要记得对照新版。真正需要精确答案时,以官方手册为准:Lua 5.5 参考手册。
一个实用的建议:别在版本选择上纠结太久。先拿 5.5 把基础语法敲一遍,等真正写游戏插件或网关扩展时,再按宿主环境锁定的版本回退。
新手最容易踩的坑
- 下标从 1 开始。 从 Python、C 转 Lua 的人写
t[0]不报错,拿到的是 nil,然后整个循环空转。数组、字符串下标全部从 1 数起。 - 漏写 local 就是无声的全局。 函数里一个变量忘了
local,它就成了全局,两个函数互相污染还查不出原因。用 luacheck 事先拦截,上 5.5 就用global声明把这类错误变成显式报错。 - 井号长度只对“无洞数组”有意义。 表里有 nil 空洞时,
#t返回哪一侧边界是未定义行为,可能每次不同。需要准确长度就自己维护一个计数,或用table.pack的 n 字段。 - ipairs 见 nil 就停。 数组中段混进 nil,
ipairs会提前结束遍历。要完整遍历又可能含 nil 时,用数字 for 配合已知长度。 - pairs 不保证顺序。 想按顺序输出就先
table.sort一份键列表。 - 循环里大量字符串拼接性能很差。 字符串在 Lua 里不可变,
..在循环里每轮都生成新串,用table.concat把片段攒进表再一次性连接。 - 整数溢出会回绕。 5.3 起的 64 位整数运算溢出时不报错,直接环绕。别用 Lua 算天文数字与密码学场景的大数。
- 给宿主写脚本先确认版本。 Neovim 是 LuaJIT(5.1 语义),Redis 是 5.1,LÖVE 用官方 Lua。把 5.3 之后才有的
//、位运算、goto写进 LuaJIT 环境,直接语法报错。 - 元方法里小心无限递归。
__index里再用普通索引会再次触发元方法,必要时改用rawget与rawset绕开元表。
和 Python 比,什么时候该选 Lua
这两个常被放在一起问,答案其实不复杂:看你想让它当主角还是当配角。
| 使用场景 | 更合适的选择 |
|---|---|
| 数据处理、爬虫、自动化 | Python |
| 数据分析与 AI | Python |
| Web 后端与业务系统 | Python |
| 游戏脚本、插件、热更新 | Lua |
| 嵌进 C 或 C++ 宿主程序 | Lua |
| 资源受限的嵌入式环境 | Lua |
一句话总结:不确定的时候先学 Python;明确是游戏脚本,或者要给一个已有的宿主程序加扩展逻辑,Lua 更顺手。
写在最后
Lua 不是那种能拿去面试里吹一小时的热门语言。它的价值在于位置感很强:清楚自己该待在哪一层,然后把那一层做到足够轻、足够快。2025 年底的 5.5 没有给语言堆新概念,反而在内存、延迟、安全性这些地方补了短板,对一门三十多岁的语言来说,这种克制挺难得。
Lua 的“精通”从来不是背下多少语言特性——它的语言特性一只手数得完——而是三件事:把表和元表的组合玩熟,把协程的调度想透,把宿主环境摸清。2026 年这一版 5.5 把最常见的运行时哑弹(隐式全局)变成了显式错误,学它的时机比任何时候都好。选一个宿主动手,比读十篇教程都管用。
入门与进阶资源清单
- Lua 官网与全部官方资料:lua.org
- Lua 5.5 在线参考手册:Lua 5.5 Reference Manual
- Programming in Lua(第一版免费在线,第四版覆盖 5.3):Programming in Lua
- 官方入门教程:Lua: getting started
- lua-users 社区 wiki:lua-users.org
- LuaJIT 官网:luajit.org
- LÖVE 游戏引擎:love2d.org
- Luau 方言仓库:luau-lang/luau
- LuaRocks 包管理器:luarocks.org
- lua-l 官方邮件列表:Lua mailing lists