Luau 速查表
类型检查模式
文件第一行的注释决定 Luau 检查得有多严格。
| 模式 | 语法 |
|---|---|
| 不做类型检查 | --!nocheck |
| 默认:只检查有注解的部分 | --!nonstrict |
| 严格:推断并检查所有内容 | --!strict |
| 运行文件 | luau main.luau |
| 只做类型检查,不运行 | luau-analyze main.luau |
| 关闭某一条 lint 警告 | --!nolint UnknownGlobal |
基本类型
基本类型,以及如何给变量加类型注解。
| 操作 | 语法 |
|---|---|
| 数字 | local age: number = 25 |
| 字符串 | local name: string = "Ada" |
| 布尔值 | local ok: boolean = true |
| 可选类型(可能为 nil) | local nick: string? = nil |
| Any(不做检查) | local x: any = f() |
| Unknown(更安全的 any) | local x: unknown = f() |
| Never(总是抛出错误) | function fail(): never error("boom") end |
| Nil | local n: nil = nil |
| 协程 / buffer | local co: thread, local b: buffer |
| 类型推断:不需要注解 | local hp = 100 -- number |
带类型的 table:数组与 map
table 依然是 Lua 的 table,类型只是说明里面装的是什么。
| 操作 | 语法 |
|---|---|
| 数字数组 | local xs: {number} = {1, 2, 3} |
| 字符串数组 | local names: {string} = {} |
| map(字符串键) | local ages: {[string]: number} = {} |
| map(任意键类型) | local seen: {[Player]: boolean} = {} |
| 嵌套数组 | local grid: {{number}} = {} |
| 结构体数组 | local items: {{id: number}} = {} |
| 只读数组 | local t = table.freeze({1, 2, 3}) |
| 通用遍历(Luau 独有) | for i, v in xs do print(i, v) end |
| 遍历键和值 | for k, v in pairs(ages) do end |
函数
给参数和返回值加类型注解;-> () 表示没有返回值。
| 操作 | 语法 |
|---|---|
| 带类型的参数和返回值 | function add(a: number, b: number): number |
| 无返回值 | function log(msg: string): () end |
| 返回多个值 | function split(s: string): (string, string) |
| 可选参数 | function greet(name: string?) end |
| 默认值 | function greet(name: string?) name = name or "friend" end |
| 可变参数 | function sum(...: number): number end |
| 匿名函数 | local f = function(x: number): number return x * 2 end |
| 函数类型 | type Adder = (number, number) -> number |
| 回调参数 | function each(f: (number) -> ()) end |
类型别名、联合类型与字面量类型
给类型起一次名字,到处都能复用。
| 操作 | 语法 |
|---|---|
| 给基本类型起别名 | type Health = number |
| 联合类型 | type Id = number | string |
| 可选类型就是与 nil 的联合 | type Maybe = string | nil -- same as string? |
| 字面量(单例)类型 | type Dir = "up" | "down" |
| 交叉类型 | type Both = Named & Aged |
| 泛型别名 | type List<T> = {T} |
| 从模块导出 | export type Point = { x: number, y: number } |
| 取现有值的类型 | type Config = typeof(config) |
| table 类型的所有键 | type K = keyof<Point> |
table 结构与方法
描述对象的结构,以及冒号调用带来的 self。
| 操作 | 语法 |
|---|---|
| 内联结构 | local p: { x: number, y: number } = { x = 0, y = 0 } |
| 命名结构 | type Point = { x: number, y: number } |
| 可选属性 | type User = { name: string, age: number? } |
| 结构 + 索引器 | type Bag = { count: number, [string]: any } |
| 扩展结构 | type Admin = User & { level: number } |
| 方法(冒号简写) | function Point.move(self: Point, dx: number) end |
| 类型中的方法 | type Point = { move: (self: Point, dx: number) -> () } |
| 构造函数写法 | function Point.new(x: number): Point end |
| 冻结的常量(类似枚举) | local Color = table.freeze({ Red = 1, Blue = 2 }) |
泛型
一个函数或类型,适用于多种值类型。
| 操作 | 语法 |
|---|---|
| 泛型函数 | function id<T>(x: T): T return x end |
| 两个类型参数 | function pair<A, B>(a: A, b: B): (A, B) end |
| 针对数组的泛型 | function first<T>(xs: {T}): T? return xs[1] end |
| 泛型类型别名 | type Stack<T> = { items: {T} } |
| 实例化别名 | local s: Stack<string> = { items = {} } |
| 泛型 map 函数 | function map<T, U>(xs: {T}, f: (T) -> U): {U} end |
| 泛型包(可变参数) | function call<T...>(f: () -> T...): T... end |
类型收窄、断言与守卫
让类型检查器相信,这个值就是你知道的那种类型。
| 操作 | 语法 |
|---|---|
| 类型断言(转换) | local n = value :: number |
| 经由 any 断言 | local n = (value :: any) :: number |
| 用 typeof 收窄 | if typeof(x) == "string" then -- x is string end |
| 收窄可选类型 | if name then print(#name) end |
| 为 nil 时提前返回 | if not name then return end |
| 收窄字面量联合类型 | if dir == "up" then end |
| 运行时检查 + 类型错误 | assert(typeof(id) == "number", "id must be a number") |
| 断言不为 nil 后再使用 | local item = assert(find(id), "missing item") |
Lua 没有的运算符与语法
这些日常用得上的便利写法,让 Luau 代码看起来不再像 Lua。
| 操作 | 语法 |
|---|---|
| 加后赋值 | hp += 10 |
| 减 / 乘 / 除后赋值 | hp -= 5, dmg *= 2, dmg /= 2 |
| 整除后赋值 | n //= 2 |
| 取模 / 乘方后赋值 | n %= 3, n ^= 2 |
| 拼接后赋值 | msg ..= "!" |
| 整除 | local half = 7 // 2 -- 3 |
| 字符串插值 | Hello, {name}! You have {n} items.(用反引号包起来) |
| 插入表达式 | Total: {price * qty}(用反引号包起来) |
| 跳过本次循环 | for _, v in xs do if v < 0 then continue end end |
| if-then-else 表达式 | local label = if hp > 0 then "alive" else "dead" |
| 链式 else-if 表达式 | local t = if n > 0 then "pos" elseif n < 0 then "neg" else "zero" |
| 二进制 / 带分隔符的数字字面量 | 0b1010, 1_000_000 |
新增库函数与和 Lua 的区别
Luau 新增的标准库函数,以及它移除的 Lua 特性。
| 项目 | 说明 |
|---|---|
| 在数组中查找值 | table.find(xs, 42) -- index or nil |
| 浅拷贝 | local copy = table.clone(t) |
| 设为不可变 / 检查 | table.freeze(t), table.isfrozen(t) |
| 预分配数组 | table.create(10, 0) |
| 限制范围 / 四舍五入 / 取符号 | math.clamp(x, 0, 1), math.round(x), math.sign(x) |
| 位运算 | bit32.band(a, b), bit32.lshift(a, 1) |
| 为沙箱安全而移除 | io, package, loadstring, loadfile, dofile |
| 已弃用但仍可用 | getfenv, setfenv(会让优化失效) |
| Luau 中没有 | goto / 标签(Luau 基于 Lua 5.1) |
| 在 Coddy 上读取标准输入 | io.read() 在这里可以用,Coddy 在 Luau 之上恢复了它 |
| 文件扩展名 | main.luau(.lua 也可以) |
| Roblox 独有,不属于核心 Luau | game, workspace, Instance.new, task.wait |
Luau 就是 Lua 5.1 加上一套渐进式类型系统,由 Roblox 创建并开源。这份 Luau 速查表讲的是普通 Lua 里没有的部分:类型注解、带类型的 table、联合类型和字面量类型、泛型、类型断言,以及 Luau 新增的语法糖(+=、字符串插值、continue、if-then-else 表达式)。两种语言共有的内容,比如 table、元表、协程和 string 库,请对照旁边的 Lua 速查表。
这里的每个代码片段都是标准 Luau,可以用 luau 命令行运行,也可以用 luau-analyze 做类型检查。需要什么就复制什么,也可以在 Luau 在线编辑器里直接运行,无需安装。想从零学会类型系统?Coddy 的免费交互式 Luau 课程会一课一课带你掌握。
Luau 速查表常见问题
这份 Luau 速查表免费吗?
免费。这份 Luau 速查表完全免费,也不用注册。把它加入书签,下次需要查联合类型、泛型或带类型 table 的写法时就能直接打开。
Luau 有 += 运算符吗?
+=、-=、*=、/=、//=、%=、^=,还有用于字符串拼接的 ..=。所以 hp += 10 是合法的 Luau 代码(也是合法的 Roblox 脚本),而在 Lua 5.1 中你必须写成 hp = hp + 10。注意,Luau 中没有 ++ 或 --。Lua 和 Luau 有什么区别?
continue、if-then-else 表达式、整除 //、通用遍历(for i, v in t do),以及 table.find、table.clone、table.freeze 等库函数。为了沙箱安全,它移除了 loadstring、loadfile、dofile 以及 io 和 package 库;又因为基于 5.1,它也没有 goto。大多数普通 Lua 代码不用修改就能作为 Luau 运行。怎么在 Luau 中开启类型检查?
--!strict 会检查所有内容并积极推断类型,--!nonstrict(默认)只检查你加了注解的部分,--!nocheck 则关闭检查。在编辑器之外,运行 luau-analyze main.luau 可以只做类型检查而不执行代码,运行 luau main.luau 则会执行它。Luau 里也像 TypeScript 那样写 interface 吗?
interface 关键字。对象的结构用 table 类型来写,再用 type 起个名字:type User = { name: string, age: number? }。组合多个结构时用 &,而不是 extends;想让其他模块也能用,就写 export type。这份速查表和 Roblox 脚本是一回事吗?
game、workspace、Instance.new、各种服务和事件),它是建立在语言之上的一个库,由 Roblox 而不是 Luau 提供文档。Roblox 游戏脚本课程会教你这套 API,你写的脚本会在浏览器里的一个 Roblox 风格小游戏中运行。在哪里可以运行这些 Luau 代码片段?
luau 解释器,并支持标准输入,你可以把本页的任意一行粘贴进去,然后点击“运行”。想系统地练习,交互式 Luau 课程用练习、项目和证书覆盖了同样的内容。