roblox-luau-core

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Luau Core Language

Luau核心语言

When to Use

使用场景

Load this skill when the task involves:
  • General Luau syntax, variables, operators, or control flow
  • Table operations (arrays, dictionaries, iteration, manipulation)
  • String patterns and string library usage
  • Math operations and helpers
  • Scope, closures, and variable lifetime
  • Common idioms and their pitfalls
  • Porting code from JavaScript/Python/other languages to Luau
  • Understanding sharp edges (1-based indexing, nil semantics, truthiness)
Hand off to other skills when:
  • Type annotations, generics, inference, strictness modes →
    roblox-luau-types
  • OOP patterns, async/promises, module architecture, service patterns →
    roblox-luau-patterns
  • Roblox engine APIs, services, networking, data storage →
    roblox-*
    domain skills
当任务涉及以下内容时加载此技能:
  • Luau通用语法、变量、运算符或控制流
  • Table操作(数组、字典、迭代、处理)
  • 字符串模式与字符串库使用
  • 数学运算与辅助函数
  • 作用域、闭包与变量生命周期
  • 常见编程惯用法及其陷阱
  • 将JavaScript/Python/其他语言的代码移植到Luau
  • 理解注意事项(1-based索引、nil语义、真值判断)
需转交其他技能的场景:
  • 类型注解、泛型、类型推断、严格模式 →
    roblox-luau-types
  • OOP模式、异步/承诺、模块架构、服务模式 →
    roblox-luau-patterns
  • Roblox引擎API、服务、网络、数据存储 →
    roblox-*
    领域技能

Decision Rules

决策规则

  • Stay within pure Luau syntax, semantics, and standard library
  • Treat Luau as Lua 5.1 + Luau extensions. Do NOT assume Lua 5.2+ features
  • Prefer
    local
    variables and
    local function
    by default
  • Keep control flow direct:
    if
    /
    elseif
    /
    else
    , loops,
    break
    ,
    continue
    over clever boolean tricks
  • Use Luau-specific syntax when it improves correctness/readability (if-expressions, compound assignment, generalized iteration)
  • Prefer built-in library functions over handwritten helpers
  • When unsure whether something is a language question or an engine question, answer only the pure Luau portion
  • 仅涉及纯Luau语法、语义与标准库
  • 将Luau视为Lua 5.1 + Luau扩展。请勿假设支持Lua 5.2+特性
  • 默认优先使用
    local
    变量与
    local function
  • 控制流保持直接:使用
    if
    /
    elseif
    /
    else
    、循环、
    break
    continue
    ,而非复杂的布尔技巧
  • 当Luau特定语法能提升正确性/可读性时使用(if表达式、复合赋值、通用迭代)
  • 优先使用内置库函数而非手写辅助函数
  • 若不确定问题属于语言层面还是引擎层面,仅回答纯Luau相关部分

Key Rules

关键规则

  • Luau is NOT Lua 5.1. Has: generics,
    continue
    ,
    +=
    , string interpolation (backticks), floor division
    //
  • Arrays are 1-based.
    #tbl
    for length. Generalized iteration:
    for k, v in tbl do
  • Always use
    task.wait/spawn/delay
    (never deprecated
    wait/spawn/delay
    )
  • Prefer backtick interpolation over
    ..
    concatenation
  • Local function order: callees above callers (no hoisting). Forward-declare for mutual recursion.
  • Only
    nil
    and
    false
    are falsy.
    0
    ,
    ""
    , and
    {}
    are truthy.

  • Luau并非Lua 5.1。新增特性:泛型、
    continue
    +=
    、字符串插值(反引号)、地板除法
    //
  • 数组为1-based索引。使用
    #tbl
    获取长度。通用迭代:
    for k, v in tbl do
  • 始终使用
    task.wait/spawn/delay
    (绝不使用已废弃的
    wait/spawn/delay
  • 优先使用反引号插值而非
    ..
    拼接
  • 局部函数顺序:被调用函数需在调用函数之前声明(无变量提升)。相互递归时需提前声明
  • nil
    false
    为假值。
    0
    ""
    {}
    均为真值

Luau Extensions (not in Lua 5.1)

Luau扩展特性(Lua 5.1中不存在)

luau
-- Compound assignment operators
score += 10
score -= 5
score *= 2

-- continue keyword (skips to next iteration)
for i = 1, 10 do
    if i % 2 == 0 then continue end
    print(i)
end

-- Generalized iteration (preferred over ipairs/pairs)
for index, item in items do print(index, item) end
for key, value in stats do print(key, value) end
luau
-- 复合赋值运算符
score += 10
score -= 5
score *= 2

-- continue关键字(跳过当前迭代,进入下一次)
for i = 1, 10 do
    if i % 2 == 0 then continue end
    print(i)
end

-- 通用迭代(优先于ipairs/pairs)
for index, item in items do print(index, item) end
for key, value in stats do print(key, value) end

Tables

Table

Tables are the only compound data structure. They serve as arrays, dictionaries, objects, and namespaces.
luau
-- Dictionary (string keys)
-- NOTE: name = "Alice" is shorthand for ["name"] = "Alice".
-- Luau tables are NOT JSON objects. Keys are strings, not identifiers.
local player = {
    name = "Alice",
    health = 100,
    inventory = {},
}
print(player.name)       --> "Alice"
print(player["health"])  --> 100

-- Dynamic keys REQUIRE bracket notation
local fieldName = "health"
print(player[fieldName]) --> 100

-- Arrays are 1-based, NOT 0-based
local items = { "sword", "shield", "potion" }
print(items[1]) --> "sword"
print(#items)   --> 3 (length operator)
Table是Luau中唯一的复合数据结构,可作为数组、字典、对象与命名空间使用。
luau
-- 字典(字符串键)
-- 注意:name = "Alice"是["name"] = "Alice"的简写。
-- Luau的Table并非JSON对象。键为字符串,而非标识符。
local player = {
    name = "Alice",
    health = 100,
    inventory = {},
}
print(player.name)       --> "Alice"
print(player["health"])  --> 100

-- 动态键必须使用方括号语法
local fieldName = "health"
print(player[fieldName]) --> 100

-- 数组为1-based索引,而非0-based
local items = { "sword", "shield", "potion" }
print(items[1]) --> "sword"
print(#items)   --> 3(长度运算符)

Table Operations

Table操作

luau
-- table.insert: append to array
local queue = {}
table.insert(queue, "task1")
table.insert(queue, "task2")
-- queue = {"task1", "task2"}

-- table.insert at index: insert at position (shifts others right)
table.insert(queue, 1, "urgent")
-- queue = {"urgent", "task1", "task2"}

-- table.remove: remove by index (shifts others left), returns removed value
local removed = table.remove(queue, 1) --> "urgent"

-- table.remove without index removes last element
local last = table.remove(queue) --> "task2"

-- table.find: search for value in array (returns index or nil)
local fruits = { "apple", "banana", "cherry" }
local index = table.find(fruits, "banana") --> 2
local missing = table.find(fruits, "grape") --> nil

-- table.sort: in-place sort
local numbers = { 5, 3, 8, 1, 9 }
table.sort(numbers) -- ascending by default
-- numbers = {1, 3, 5, 8, 9}

-- Custom sort comparator
local players = {
    { name = "Alice", score = 150 },
    { name = "Bob", score = 200 },
    { name = "Charlie", score = 100 },
}
table.sort(players, function(a, b)
    return a.score > b.score -- descending by score
end)

-- table.concat: join array elements into string
local parts = { "Hello", "world", "!" }
print(table.concat(parts, " ")) --> "Hello world !"

-- table.freeze / table.isfrozen (Luau extension - immutable tables)
local CONFIG = table.freeze({
    MAX_PLAYERS = 50,
    ROUND_TIME = 300,
    MAP_SIZE = 500,
})
-- CONFIG.MAX_PLAYERS = 100 --> ERROR: attempt to modify a frozen table

-- table.clone (Luau extension - shallow copy)
local original = { 1, 2, 3, sub = { 4, 5 } }
local copy = table.clone(original)
copy[1] = 99
print(original[1]) --> 1 (not affected)
-- NOTE: sub-tables are still shared references (shallow copy)

-- table.move (copy elements between tables or within a table)
local src = { 10, 20, 30, 40, 50 }
local dst = {}
table.move(src, 2, 4, 1, dst) -- copy src[2..4] into dst starting at dst[1]
-- dst = {20, 30, 40}

-- table.clear (Luau extension - remove all keys, keep table reference)
local t = { 1, 2, 3 }
table.clear(t) -- t is now empty but same reference

-- Deep copy utility (not built-in - write your own)
local function deepCopy<T>(original: T): T
    if typeof(original) ~= "table" then
        return original
    end
    local copy = table.clone(original :: any)
    for key, value in copy do
        if typeof(value) == "table" then
            copy[key] = deepCopy(value)
        end
    end
    return copy :: T
end
luau
-- table.insert:向数组末尾添加元素
local queue = {}
table.insert(queue, "task1")
table.insert(queue, "task2")
-- queue = {"task1", "task2"}

-- table.insert指定索引:在指定位置插入元素(后续元素右移)
table.insert(queue, 1, "urgent")
-- queue = {"urgent", "task1", "task2"}

-- table.remove:按索引删除元素(后续元素左移),返回被删除的值
local removed = table.remove(queue, 1) --> "urgent"

-- table.remove不指定索引时删除最后一个元素
local last = table.remove(queue) --> "task2"

-- table.find:在数组中搜索值(返回索引或nil)
local fruits = { "apple", "banana", "cherry" }
local index = table.find(fruits, "banana") --> 2
local missing = table.find(fruits, "grape") --> nil

-- table.sort:原地排序
local numbers = { 5, 3, 8, 1, 9 }
table.sort(numbers) -- 默认升序
-- numbers = {1, 3, 5, 8, 9}

-- 自定义排序比较器
local players = {
    { name = "Alice", score = 150 },
    { name = "Bob", score = 200 },
    { name = "Charlie", score = 100 },
}
table.sort(players, function(a, b)
    return a.score > b.score -- 按分数降序
end)

-- table.concat:将数组元素拼接为字符串
local parts = { "Hello", "world", "!" }
print(table.concat(parts, " ")) --> "Hello world !"

-- table.freeze / table.isfrozen(Luau扩展 - 不可变Table)
local CONFIG = table.freeze({
    MAX_PLAYERS = 50,
    ROUND_TIME = 300,
    MAP_SIZE = 500,
})
-- CONFIG.MAX_PLAYERS = 100 --> 错误:尝试修改冻结的table

-- table.clone(Luau扩展 - 浅拷贝)
local original = { 1, 2, 3, sub = { 4, 5 } }
local copy = table.clone(original)
copy[1] = 99
print(original[1]) --> 1(不受影响)
-- 注意:子table仍为共享引用(浅拷贝)

-- table.move(在table之间或table内部复制元素)
local src = { 10, 20, 30, 40, 50 }
local dst = {}
table.move(src, 2, 4, 1, dst) -- 将src[2..4]复制到dst,从dst[1]开始
-- dst = {20, 30, 40}

-- table.clear(Luau扩展 - 删除所有键,保留table引用)
local t = { 1, 2, 3 }
table.clear(t) -- t现在为空,但引用不变

-- 深拷贝工具(非内置 - 需自行实现)
local function deepCopy<T>(original: T): T
    if typeof(original) ~= "table" then
        return original
    end
    local copy = table.clone(original :: any)
    for key, value in copy do
        if typeof(value) == "table" then
            copy[key] = deepCopy(value)
        end
    end
    return copy :: T
end

String Interpolation

字符串插值

luau
-- ALWAYS prefer backtick interpolation over .. concatenation
local name = "Alice"
local level = 42
local message = `{name} reached level {level}!`

-- Expressions in interpolation
local price = 19.99
local tax = 0.08
print(`Total: ${price * (1 + tax)}`)

-- string.split (Luau extension)
local parts = string.split("a,b,c", ",")
luau
-- 始终优先使用反引号插值而非..拼接
local name = "Alice"
local level = 42
local message = `{name} reached level {level}!`

-- 插值中使用表达式
local price = 19.99
local tax = 0.08
print(`Total: ${price * (1 + tax)}`)

-- string.split(Luau扩展)
local parts = string.split("a,b,c", ",")

String Patterns

字符串模式

Luau uses Lua patterns, which are NOT regular expressions. They are simpler and more limited.
luau
-- Character classes
-- %a  letters          %A  non-letters
-- %d  digits           %D  non-digits
-- %l  lowercase        %L  non-lowercase
-- %u  uppercase        %U  non-uppercase
-- %w  alphanumeric     %W  non-alphanumeric
-- %s  whitespace       %S  non-whitespace
-- %p  punctuation      %P  non-punctuation
-- .   any character
-- %%  literal %

-- Quantifiers
-- *   0 or more (greedy)
-- +   1 or more (greedy)
-- -   0 or more (lazy)
-- ?   0 or 1

-- string.match: extract matches
local year, month, day = string.match("2026-03-04", "(%d+)-(%d+)-(%d+)")
print(year, month, day) --> "2026" "03" "04"

-- string.gmatch: iterate over all matches
local text = "score=100, level=42, health=75"
for key, value in string.gmatch(text, "(%w+)=(%d+)") do
    print(key, value)
end

-- string.gsub: replace matches
local cleaned = string.gsub("Hello   World", "%s+", " ")
print(cleaned) --> "Hello World"

-- Escaping pattern characters: use % before special chars
-- Special chars: ( ) . % + - * ? [ ] ^ $
local escaped = string.gsub("file.txt", "%.", "_")
print(escaped) --> "file_txt"

-- Anchors
-- ^ matches start of string
-- $ matches end of string
local isEmail = string.match("user@example.com", "^%w+@%w+%.%w+$") ~= nil
Luau使用Lua模式,而非正则表达式。Lua模式更简单、功能更有限。
luau
-- 字符类
-- %a  字母          %A  非字母
-- %d  数字           %D  非数字
-- %l  小写字母        %L  非小写字母
-- %u  大写字母        %U  非大写字母
-- %w  字母数字        %W  非字母数字
-- %s  空白字符        %S  非空白字符
-- %p  标点符号        %P  非标点符号
-- .   任意字符
-- %%  字面量%

-- 量词
-- *   0次或多次(贪婪)
-- +   1次或多次(贪婪)
-- -   0次或多次(惰性)
-- ?   0次或1次

-- string.match:提取匹配内容
local year, month, day = string.match("2026-03-04", "(%d+)-(%d+)-(%d+)")
print(year, month, day) --> "2026" "03" "04"

-- string.gmatch:遍历所有匹配项
local text = "score=100, level=42, health=75"
for key, value in string.gmatch(text, "(%w+)=(%d+)") do
    print(key, value)
end

-- string.gsub:替换匹配内容
local cleaned = string.gsub("Hello   World", "%s+", " ")
print(cleaned) --> "Hello World"

-- 转义模式字符:在特殊字符前加%
-- 特殊字符:( ) . % + - * ? [ ] ^ $
local escaped = string.gsub("file.txt", "%.", "_")
print(escaped) --> "file_txt"

-- 锚点
-- ^ 匹配字符串开头
-- $ 匹配字符串结尾
local isEmail = string.match("user@example.com", "^%w+@%w+%.%w+$") ~= nil

Luau-Specific Math Extensions

Luau特定数学扩展

luau
local intDiv = 10 // 3   --> 3 (floor division, Luau extension)
print(math.clamp(15, 0, 10))  --> 10 (Luau extension)
print(math.sign(-7))          --> -1 (Luau extension)
print(math.round(3.5))        --> 4 (Luau extension)

-- For better randomness, use Random.new()
local rng = Random.new()
print(rng:NextNumber())         --> [0, 1) float
print(rng:NextInteger(1, 100))  --> [1, 100] integer
luau
local intDiv = 10 // 3   --> 3(地板除法,Luau扩展)
print(math.clamp(15, 0, 10))  --> 10(Luau扩展)
print(math.sign(-7))          --> -1(Luau扩展)
print(math.round(3.5))        --> 4(Luau扩展)

-- 为获得更好的随机性,使用Random.new()
local rng = Random.new()
print(rng:NextNumber())         --> [0, 1) 浮点数
print(rng:NextInteger(1, 100))  --> [1, 100] 整数

Math Helpers

数学辅助函数

luau
-- Clamping values
local health = math.clamp(currentHealth, 0, MAX_HEALTH)

-- Linear interpolation
local function lerp(a: number, b: number, t: number): number
    return a + (b - a) * t
end

-- Mapping a value from one range to another
local function map(value: number, inMin: number, inMax: number, outMin: number, outMax: number): number
    return outMin + (outMax - outMin) * ((value - inMin) / (inMax - inMin))
end

-- Distance between two Vector3s
local distance = (posA - posB).Magnitude

-- Normalized direction
local direction = (target - origin).Unit

-- Rounding to decimal places
local function roundTo(value: number, places: number): number
    local factor = 10 ^ places
    return math.round(value * factor) / factor
end
print(roundTo(3.14159, 2)) --> 3.14
luau
-- 值钳制
local health = math.clamp(currentHealth, 0, MAX_HEALTH)

-- 线性插值
local function lerp(a: number, b: number, t: number): number
    return a + (b - a) * t
end

-- 将值从一个范围映射到另一个范围
local function map(value: number, inMin: number, inMax: number, outMin: number, outMax: number): number
    return outMin + (outMax - outMin) * ((value - inMin) / (inMax - inMin))
end

-- 两个Vector3之间的距离
local distance = (posA - posB).Magnitude

-- 归一化方向
local direction = (target - origin).Unit

-- 四舍五入到指定小数位
local function roundTo(value: number, places: number): number
    local factor = 10 ^ places
    return math.round(value * factor) / factor
end
print(roundTo(3.14159, 2)) --> 3.14

Common Idioms

常见编程惯用法

Ternary with and/or

使用and/or实现三元表达式

Luau has no ternary operator. Use
and
/
or
chains for single-value conditions:
luau
-- Basic ternary: condition and truthy_value or falsy_value
local status = (health > 0 and "alive" or "dead")
local label = (isAdmin and "Admin" or "User")
local color = (isActive and Color3.new(0, 1, 0) or Color3.new(1, 0, 0))

-- With function calls
local displayName = (player.DisplayName ~= "" and player.DisplayName or player.Name)

-- Nested (use sparingly - readability drops fast)
local tier = (score >= 90 and "S" or score >= 70 and "A" or score >= 50 and "B" or "C")

-- CAVEAT: if the truthy value is nil or false, the expression breaks:
-- (condition and nil or "fallback") returns "fallback" even when condition is true
-- In that case, use a proper if/else block or Luau's if-expression:
local result = if condition then valueA else valueB

Luau没有三元运算符。对单值条件使用
and
/
or
链式表达式:
luau
-- 基础三元表达式:condition and 真值 or 假值
local status = (health > 0 and "alive" or "dead")
local label = (isAdmin and "Admin" or "User")
local color = (isActive and Color3.new(0, 1, 0) or Color3.new(1, 0, 0))

-- 结合函数调用
local displayName = (player.DisplayName ~= "" and player.DisplayName or player.Name)

-- 嵌套(尽量少用 - 可读性会快速下降)
local tier = (score >= 90 and "S" or score >= 70 and "A" or score >= 50 and "B" or "C")

-- 注意:如果真值为nil或false,表达式会失效:
-- (condition and nil or "fallback") 即使condition为true,也会返回"fallback"
-- 这种情况下,使用标准if/else块或Luau的if表达式:
local result = if condition then valueA else valueB

Sharp Edges

注意事项

1-Based Indexing

1-Based索引

Luau arrays are 1-indexed. The first element is
array[1]
, not
array[0]
.
luau
local items = { "first", "second", "third" }
print(items[1]) --> "first"
print(items[0]) --> nil (NOT an error, just nil)

-- Off-by-one errors are common when porting from other languages
for i = 1, #items do -- correct: 1 to length
    print(items[i])
end
Luau数组为1-based索引。第一个元素是
array[1]
,而非
array[0]
luau
local items = { "first", "second", "third" }
print(items[1]) --> "first"
print(items[0]) --> nil(不是错误,仅返回nil)

-- 从其他语言移植时容易出现差一错误
for i = 1, #items do -- 正确:从1到数组长度
    print(items[i])
end

The
#
Operator and Nil Gaps

#
运算符与nil间隙

The
#
(length) operator is only reliable for contiguous arrays with no nil gaps.
luau
-- Reliable: contiguous array
local a = { 1, 2, 3, 4, 5 }
print(#a) --> 5 (correct)

-- UNRELIABLE: array with nil gap
local b = { 1, 2, nil, 4, 5 }
print(#b) --> could be 2 or 5 (undefined behavior!)

-- The length operator finds ANY valid boundary where t[n] ~= nil and t[n+1] == nil
-- With gaps, multiple boundaries exist, and the result is unpredictable

-- SAFE: if you need to handle sparse data, use a dictionary with explicit count
local sparse: { [number]: string } = {}
local count = 0
sparse[1] = "a"
count += 1
sparse[5] = "e"
count += 1
-- Use count, not #sparse
#
(长度)运算符仅在无nil间隙的连续数组中可靠。
luau
-- 可靠:连续数组
local a = { 1, 2, 3, 4, 5 }
print(#a) --> 5(正确)

-- 不可靠:存在nil间隙的数组
local b = { 1, 2, nil, 4, 5 }
print(#b) --> 可能为2或5(行为未定义!)

-- 长度运算符会寻找任意有效的边界,满足t[n] ~= nil且t[n+1] == nil
-- 存在间隙时,可能有多个边界,结果不可预测

-- 安全做法:如果需要处理稀疏数据,使用带显式计数的字典
local sparse: { [number]: string } = {}
local count = 0
sparse[1] = "a"
count += 1
sparse[5] = "e"
count += 1
-- 使用count,而非#sparse

Nil in Tables

Table中的nil

luau
-- Setting a table value to nil REMOVES the key
local t = { a = 1, b = 2, c = 3 }
t.b = nil
-- t is now { a = 1, c = 3 } - "b" key no longer exists

-- This means you cannot store nil as a meaningful value in a table
-- Use a sentinel value instead if you need to distinguish "absent" from "nil"
local NONE = newproxy(false) -- unique sentinel
local cache = {}
cache["key"] = NONE -- means "we checked, value is absent"
-- cache["other"] is nil, meaning "we haven't checked yet"

-- nil in arrays causes gaps (see # operator issue above)
local list = { 1, 2, 3 }
list[2] = nil -- creates a gap - DO NOT DO THIS
-- Use table.remove(list, 2) instead to shift elements down
luau
-- 将Table值设为nil会删除对应的键
local t = { a = 1, b = 2, c = 3 }
t.b = nil
-- t现在为{ a = 1, c = 3 } - "b"键已不存在

-- 这意味着无法在Table中存储nil作为有意义的值
-- 如果需要区分“不存在”与“nil”,请使用标记值
local NONE = newproxy(false) -- 唯一标记值
local cache = {}
cache["key"] = NONE -- 表示“已检查,值不存在”
-- cache["other"]为nil,表示“尚未检查”

-- 数组中的nil会造成间隙(见上文#运算符问题)
local list = { 1, 2, 3 }
list[2] = nil -- 创建间隙 - 请勿这样做
-- 应使用table.remove(list, 2)来让后续元素左移

Equality and Type Coercion

相等性与类型转换

luau
-- Luau does NOT coerce types in comparisons (unlike JavaScript)
print(0 == "0")    --> false
print(1 == true)   --> false
print("" == false) --> false

-- Only nil and false are falsy
-- 0, "", and empty tables are TRUTHY
if 0 then print("0 is truthy") end         --> prints
if "" then print("empty string is truthy") end --> prints
if {} then print("empty table is truthy") end  --> prints

-- This means you cannot use `if value then` to check for empty strings or zero
-- Be explicit:
if value ~= nil and value ~= "" then end
if value ~= nil and value ~= 0 then end
luau
-- Luau在比较时不会进行类型转换(与JavaScript不同)
print(0 == "0")    --> false
print(1 == true)   --> false
print("" == false) --> false

-- 仅nil与false为假值
-- 0、""与空Table均为真值
if 0 then print("0是真值") end         --> 会打印
if "" then print("空字符串是真值") end --> 会打印
if {} then print("空Table是真值") end  --> 会打印

-- 这意味着不能使用`if value then`来检查空字符串或0
-- 需显式检查:
if value ~= nil and value ~= "" then end
if value ~= nil and value ~= 0 then end

Table Reference Semantics

Table引用语义

luau
-- Tables are passed and assigned by REFERENCE, not by value
local original = { 1, 2, 3 }
local alias = original
alias[1] = 99
print(original[1]) --> 99 (both point to the same table)

-- To get an independent copy, use table.clone (shallow) or a deep copy function
local copy = table.clone(original)
copy[1] = 0
print(original[1]) --> 99 (unaffected)

-- But nested tables are still shared in a shallow clone
local nested = { data = { 1, 2, 3 } }
local shallowCopy = table.clone(nested)
shallowCopy.data[1] = 99
print(nested.data[1]) --> 99 (shared reference!)
-- Use a deep copy for nested structures
luau
-- Table通过引用传递与赋值,而非值传递
local original = { 1, 2, 3 }
local alias = original
alias[1] = 99
print(original[1]) --> 99(两者指向同一个Table)

-- 如需独立副本,使用table.clone(浅拷贝)或深拷贝函数
local copy = table.clone(original)
copy[1] = 0
print(original[1]) --> 99(不受影响)

-- 但浅拷贝中的嵌套Table仍为共享引用
local nested = { data = { 1, 2, 3 } }
local shallowCopy = table.clone(nested)
shallowCopy.data[1] = 99
print(nested.data[1]) --> 99(共享引用!)
-- 嵌套结构需使用深拷贝

Scope and Closures

作用域与闭包

luau
-- Common loop closure bug
local functions = {}
for i = 1, 5 do
    functions[i] = function()
        return i
    end
end
-- In Luau, each loop iteration creates a new 'i' variable,
-- so this actually works correctly (unlike some other languages)
print(functions[1]()) --> 1
print(functions[5]()) --> 5

-- But watch out with while loops - the variable is shared
local fns = {}
local i = 1
while i <= 5 do
    fns[i] = function()
        return i
    end
    i += 1
end
print(fns[1]()) --> 6 (all functions share the same 'i' which is now 6)

-- Fix: capture the value in a local
local fns2 = {}
local j = 1
while j <= 5 do
    local captured = j
    fns2[j] = function()
        return captured
    end
    j += 1
end
print(fns2[1]()) --> 1 (correct)
luau
-- 常见循环闭包错误
local functions = {}
for i = 1, 5 do
    functions[i] = function()
        return i
    end
end
-- 在Luau中,每次循环迭代都会创建新的'i'变量,
-- 因此这段代码实际能正常工作(与某些语言不同)
print(functions[1]()) --> 1
print(functions[5]()) --> 5

-- 但while循环需注意 - 变量是共享的
local fns = {}
local i = 1
while i <= 5 do
    fns[i] = function()
        return i
    end
    i += 1
end
print(fns[1]()) --> 6(所有函数共享同一个'i',其值现在为6)

-- 修复:在局部变量中捕获值
local fns2 = {}
local j = 1
while j <= 5 do
    local captured = j
    fns2[j] = function()
        return captured
    end
    j += 1
end
print(fns2[1]()) --> 1(正确)

Local Function Declaration Order

局部函数声明顺序

Luau has no hoisting - a
local function
is invisible to code above its declaration.
luau
-- BAD: helperB is nil when functionA runs
local function functionA()
    helperB() -- ERROR: attempt to call a nil value
end

local function helperB()
    print("helper")
end

-- GOOD: callee declared before caller
local function helperB()
    print("helper")
end

local function functionA()
    helperB() -- works
end
For mutual recursion (A calls B, B calls A), use a forward declaration:
luau
local functionB  -- forward declaration (declares variable, no assignment)

local function functionA(x: number)
    if x <= 0 then return end
    functionB(x - 1)
end

function functionB(x: number) -- no 'local' here (already declared above)
    if x <= 0 then return end
    functionA(x - 1)
end
Rule: Callees above callers, always. If a
local function
is called by code above its definition, that is a runtime nil-error bug.
Luau没有变量提升 -
local function
在其声明之前对代码不可见。
luau
-- 错误:functionA运行时helperB为nil
local function functionA()
    helperB() -- 错误:尝试调用nil值
end

local function helperB()
    print("helper")
end

-- 正确:被调用函数在调用函数之前声明
local function helperB()
    print("helper")
end

local function functionA()
    helperB() -- 正常工作
end
对于相互递归(A调用B,B调用A),需使用提前声明:
luau
local functionB  -- 提前声明(声明变量,未赋值)

local function functionA(x: number)
    if x <= 0 then return end
    functionB(x - 1)
end

function functionB(x: number) -- 此处不使用'local'(已在上方声明)
    if x <= 0 then return end
    functionA(x - 1)
end
规则:被调用函数始终在调用函数之前声明。如果
local function
被其声明之前的代码调用,会导致运行时nil错误。

Metatables: Powerful but Error-Prone

元表:功能强大但易出错

luau
-- Common mistake: forgetting __index
local MyClass = {}
-- Missing: MyClass.__index = MyClass

function MyClass.new()
    return setmetatable({}, MyClass)
end

function MyClass:doSomething()
    print("doing something")
end

local obj = MyClass.new()
obj:doSomething() --> ERROR: attempt to call a nil value
-- Because __index is not set, method lookup fails

-- Common mistake: modifying the metatable instead of the instance
function MyClass:setName(name: string)
    -- BAD: this sets it on the class table, shared by all instances!
    MyClass.name = name

    -- GOOD: set on the instance
    self.name = name
end
luau
-- 常见错误:忘记设置__index
local MyClass = {}
-- 缺失:MyClass.__index = MyClass

function MyClass.new()
    return setmetatable({}, MyClass)
end

function MyClass:doSomething()
    print("doing something")
end

local obj = MyClass.new()
obj:doSomething() --> 错误:尝试调用nil值
-- 因为未设置__index,方法查找失败

-- 常见错误:修改元表而非实例
function MyClass:setName(name: string)
    -- 错误:这会将值设置在类Table上,被所有实例共享!
    MyClass.name = name

    -- 正确:设置在实例上
    self.name = name
end

Reserved Keywords as Identifiers

保留关键字作为标识符

Luau reserves certain words for the language syntax. These cannot be used as identifiers:
and, break, do, else, elseif, end, false, for, function, if, in,
local, nil, not, or, repeat, return, then, true, until, while,
continue (Luau-specific)
luau
-- BAD: keyword used as parameter name - syntax error
local function onComplete(return: number) end  -- ERROR
local function process(continue: boolean) end   -- ERROR

-- GOOD: renamed to avoid reserved keyword
local function onComplete(result: number) end
local function process(shouldContinue: boolean) end

Luau保留了某些单词用于语言语法,这些单词不能用作标识符:
and, break, do, else, elseif, end, false, for, function, if, in,
local, nil, not, or, repeat, return, then, true, until, while,
continue(Luau特定)
luau
-- 错误:使用关键字作为参数名 - 语法错误
local function onComplete(return: number) end  -- 错误
local function process(continue: boolean) end   -- 错误

-- 正确:重命名以避免保留关键字
local function onComplete(result: number) end
local function process(shouldContinue: boolean) end

JS → Luau Translation Table

JavaScript → Luau转换对照表

AI models trained on JavaScript commonly generate patterns that don't exist in Luau. This table covers the most frequent mistakes.
JavaScriptLuauNotes
arr.map(fn)
table.create(#arr)
+ for loop, or use a utility
No built-in map/filter/reduce on tables
arr.filter(fn)
Loop with
table.insert
into new table
No built-in filter
arr.find(fn)
Loop with early returnNo built-in find
arr.includes(x)
table.find(arr, x) ~= nil
Returns index or nil
arr.push(x)
table.insert(arr, x)
arr.pop()
table.remove(arr)
Removes and returns last element
arr.splice(i, n)
table.remove(arr, i)
in a loop
No splice equivalent
arr.length
or
arr.length
#arr
#
operator, not a property
obj.keys(x)
No direct equivalent - use
for k in x do
obj.values(x)
for _, v in x do
Object.assign(a, b)
for k, v in b do a[k] = v end
No spread operator
const x = ...
local x = ...
No const/let/var
let x = ...
local x = ...
function(x) { return x }
function(x) return x end
No arrow functions
(x) => x * 2
function(x) return x * 2 end
No arrow functions
x === y
x == y
No
===
in Luau,
==
is strict
x !== y
x ~= y
Not
!=
null
nil
No null/undefined distinction
typeof x
typeof(x)
for Roblox types,
type(x)
for Luau types
Parentheses required
console.log(x)
print(x)
x ?? y
x or y
Luau
or
returns the value, not a boolean
x?.y
x and x.y
No optional chaining
{...obj}
Manual table copy with loopNo spread operator
[...arr]
Manual copy with loop or
table.move
No spread operator
new Map()
Regular table
{}
Luau tables are dictionaries by default
new Set()
{[value] = true}
pattern
Use table as set
Promise.all(arr)
Promise.all(arr)
Same if using evaera/Promise
async/await
coroutine
or Promise chains
No async/await syntax
try/catch
pcall(fn)
or
xpcall(fn, handler)
No try/catch
throw error
error("message")
class Foo { }
local Foo = {} Foo.__index = Foo
Prototype-based OOP
new Foo()
setmetatable({}, Foo)
import x from "y"
local x = require(y)
No ES modules
export default
return module
Module returns its public API
str1 + str2
`{str1}{str2}`
Use backtick interpolation, NOT
..
"hello " + name
`hello {name}`
Backticks are the Luau way
基于JavaScript训练的AI模型常生成Luau中不存在的模式。本表涵盖最常见的错误。
JavaScriptLuau说明
arr.map(fn)
table.create(#arr)
+ 循环,或使用工具函数
Table无内置map/filter/reduce
arr.filter(fn)
循环配合
table.insert
到新Table
无内置filter
arr.find(fn)
循环配合提前返回无内置find
arr.includes(x)
table.find(arr, x) ~= nil
返回索引或nil
arr.push(x)
table.insert(arr, x)
arr.pop()
table.remove(arr)
删除并返回最后一个元素
arr.splice(i, n)
循环调用
table.remove(arr, i)
无等效splice
arr.length
#arr
使用
#
运算符,而非属性
obj.keys(x)
无直接等效 - 使用
for k in x do
obj.values(x)
for _, v in x do
Object.assign(a, b)
for k, v in b do a[k] = v end
无扩展运算符
const x = ...
local x = ...
无const/let/var
let x = ...
local x = ...
function(x) { return x }
function(x) return x end
无箭头函数
(x) => x * 2
function(x) return x * 2 end
无箭头函数
x === y
x == y
Luau无
===
==
为严格相等
x !== y
x ~= y
不是
!=
null
nil
无null/undefined区分
typeof x
Roblox类型用
typeof(x)
,Luau类型用
type(x)
需加括号
console.log(x)
print(x)
x ?? y
x or y
Luau
or
返回值,而非布尔值
x?.y
x and x.y
无可选链
{...obj}
循环手动复制Table无扩展运算符
[...arr]
循环手动复制或使用
table.move
无扩展运算符
new Map()
普通Table
{}
LuauTable默认作为字典
new Set()
{[value] = true}
模式
使用Table作为集合
Promise.all(arr)
Promise.all(arr)
使用evaera/Promise时语法相同
async/await
coroutine
或Promise链
无async/await语法
try/catch
pcall(fn)
xpcall(fn, handler)
无try/catch
throw error
error("message")
class Foo { }
local Foo = {} Foo.__index = Foo
基于原型的OOP
new Foo()
setmetatable({}, Foo)
import x from "y"
local x = require(y)
无ES模块
export default
return module
模块返回其公开API
str1 + str2
`{str1}{str2}`
使用反引号插值,而非
..
"hello " + name
`hello {name}`
反引号是Luau的标准方式

Type-Specific Confusion

类型特定混淆

JavaScriptLuauWhy AI Gets It Wrong
0 == ""
true
0 == ""
false
Luau has no type coercion in
==
"" == false
true
"" == false
false
Only
nil
and
false
are falsy
if (0)
→ falsy
if 0 then
→ truthy
0
,
""
,
{}
are all truthy in Luau
x = null
→ typeof
object
x = nil
→ type
nil
No null/undefined split
Array.isArray(x)
type(x) == "table"
No Array type distinction
x.push()
on string
N/A - strings are not indexableNo string methods, use
string.*
library

JavaScriptLuauAI出错原因
0 == ""
true
0 == ""
false
Luau
==
无类型转换
"" == false
true
"" == false
false
nil
false
为假值
if (0)
→ 假值
if 0 then
→ 真值
Luau中
0
""
{}
均为真值
x = null
→ typeof
object
x = nil
→ type
nil
无null/undefined区分
Array.isArray(x)
type(x) == "table"
无Array类型区分
x.push()
on string
不适用 - 字符串不可索引无字符串方法,使用
string.*

Common Mistakes

常见错误

  • Treating
    0
    or
    ""
    as falsy — only
    false
    and
    nil
    are falsy in Luau
  • a and b or c
    ternary fails when
    b
    can be
    false
    or
    nil
  • Assuming zero-based arrays — Luau arrays are one-based
  • Mixing
    :
    and
    .
    syntax (defining with
    :
    , calling with
    .
    )
  • Relying on dictionary iteration order
  • Missing args become
    nil
    , extra args are silently ignored
  • Using globals where locals/closures fit
  • Overusing metatables for simple data containers
  • Assuming
    goto
    , bitwise operators, or integer semantics exist in Luau
  • Using
    ..
    concatenation instead of backtick interpolation
  • 0
    ""
    视为假值 — Luau中仅
    false
    nil
    为假值
  • b
    可能为
    false
    nil
    时,
    a and b or c
    三元表达式失效
  • 假设数组为0-based索引 — Luau数组为1-based
  • 混淆
    :
    .
    语法(用
    :
    定义,用
    .
    调用)
  • 依赖字典迭代顺序
  • 缺失参数变为
    nil
    ,多余参数被静默忽略
  • 在适合使用局部变量/闭包的场景使用全局变量
  • 对简单数据容器过度使用元表
  • 假设Luau支持
    goto
    、位运算符或整数语义
  • 使用
    ..
    拼接字符串而非反引号插值

Quality Checklist

质量检查清单

  • Valid Luau syntax (not Lua 5.2+, not JavaScript)
  • All variables declared with
    local
  • Simplest correct control-flow construct used
  • Tables used intentionally (array vs dictionary vs metatable-backed)
  • Iteration matches data shape (generalized
    for k, v in tbl do
    )
  • Standard library preferred over custom helpers
  • String building uses backtick interpolation
  • No nil gaps in arrays
  • Callees declared above callers
  • No reserved keywords used as identifiers
  • 有效的Luau语法(非Lua 5.2+,非JavaScript)
  • 所有变量均使用
    local
    声明
  • 使用最简单的正确控制流结构
  • 有意使用Table(数组、字典、元表实现)
  • 迭代方式匹配数据结构(通用
    for k, v in tbl do
  • 优先使用标准库而非自定义辅助函数
  • 字符串构建使用反引号插值
  • 数组中无nil间隙
  • 被调用函数在调用函数之前声明
  • 未使用保留关键字作为标识符",