roblox-luau-types
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLuau Type System
Luau类型系统
When to Use
使用场景
Load this skill when the task involves:
- Adding or correcting type annotations on variables, functions, tables, modules
- Choosing between ,
--!strict, and--!nonstrict--!nocheck - Designing APIs that preserve inference instead of collapsing to
any - Modeling data with generics, unions, intersections, optionals, tagged unions
- Typing object-like tables, metatable-backed modules, exported module surfaces
- Type narrowing with ,
typeof(), or conditional checksIsA() - Understanding when to annotate vs when to let inference work
- Cross-module type exports and contracts
Hand off to other skills when:
- General Luau syntax, tables, control flow, string patterns →
roblox-luau-core - OOP implementation, async patterns, module architecture →
roblox-luau-patterns - Roblox engine APIs, networking, data storage → domain skills
roblox-*
当任务涉及以下内容时加载此技能:
- 为变量、函数、表、模块添加或修正类型注解
- 在、
--!strict和--!nonstrict之间选择合适的模式--!nocheck - 设计可保留类型推断而非退化为的API
any - 使用泛型、联合类型、交叉类型、可选类型、标记联合类型建模数据
- 为类对象表、元表驱动模块、导出模块表面添加类型定义
- 使用、
typeof()或条件检查进行类型收窄IsA() - 理解何时需要注解、何时可依赖类型推断
- 跨模块的类型导出与契约定义
在以下场景移交至其他技能:
- 通用Luau语法、表、控制流、字符串模式 →
roblox-luau-core - OOP实现、异步模式、模块架构 →
roblox-luau-patterns - Roblox引擎API、网络通信、数据存储 → 领域技能
roblox-*
Decision Rules
决策规则
- Use for new or actively maintained code
--!strict - Prefer inference-preserving designs over annotation-heavy designs when inferred shape stays precise
- Annotate where it clarifies intent, stabilizes contracts, constrains , or prevents widening to
selfany - Prefer explicit exported aliases at module boundaries for stable contracts
- Use generics when input/output relationships matter; never replace with
any - Use tagged unions + refinements for multi-case structured values
- Casts () are a precision tool, not a bypass — narrow overly generic inference, don't hide errors
::
- 新代码或维护中的代码使用
--!strict - 当推断的类型形状保持精确时,优先选择保留类型推断的设计,而非大量注解的设计
- 在需要明确意图、稳定契约、约束或防止类型拓宽为
self时添加注解any - 在模块边界优先使用显式导出别名以保证稳定契约
- 当输入/输出类型存在关联时使用泛型;绝不使用替代
any - 为多场景结构化值使用标记联合类型+细化操作
- 类型转换()是精准工具而非规避手段——用于收窄过度泛化的推断类型,而非隐藏错误
::
Philosophy
核心理念
The type system exists to catch bugs at analysis time without affecting runtime. The goal is not "annotate everything" but "let the type checker help you." Key principles:
- Inference first. If the type checker already knows the type, don't annotate it. Redundant annotations add noise and can become stale.
- Annotate boundaries. Function parameters, return types, and exported module surfaces benefit from explicit types. Internal locals usually don't.
- Preserve relationships. A generic that carries a type through a transform is more valuable than
<T>that erases it.any - Narrow, don't cast. Use ,
typeof(), and conditional checks to narrow types. UseIsA()only when you genuinely know more than the checker.:: - Sealed vs unsealed matters. An annotated table is sealed (no new fields). An unannotated local table accumulates fields until it leaves scope or gets returned.
类型系统的作用是在分析阶段捕获bug,同时不影响运行时。目标并非“给所有内容加注解”,而是“让类型检查工具辅助你”。核心原则:
- 优先推断。如果类型检查器已能识别类型,无需添加注解。冗余注解会增加噪音且可能过时。
- 注解边界。函数参数、返回类型和导出模块表面适合添加显式类型。内部局部变量通常不需要。
- 保留关联。能在转换过程中传递类型的泛型比会擦除类型的
<T>更有价值。any - 优先收窄,而非转换。使用、
typeof()和条件检查收窄类型。仅当你确实比检查器更了解类型时才使用IsA()。:: - 密封与非密封的区别很重要。已注解的表是密封的(无法添加新字段)。未注解的局部表会累积字段,直到离开作用域或被返回。
Strictness Modes
严格性模式
luau
--!strict -- Full type checking. Errors on unresolved types. Use for new code.
--!nonstrict -- Default mode. Warns but allows unresolved types. Good for transitional code.
--!nocheck -- Disables type checking entirely. Only for generated code or legacy.2025-2026 Update: The New Type Solver (GA Nov 2025) is faster and more accurate. is now the default for all scripts. Prefer for anything you actively maintain.
--!nonstrict--!strictluau
--!strict -- 完整类型检查。对未解析类型报错。适用于新代码。
--!nonstrict -- 默认模式。对未解析类型发出警告但允许运行。适用于过渡代码。
--!nocheck -- 完全禁用类型检查。仅适用于生成代码或遗留代码。2025-2026更新: 新型别求解器(2025年11月正式发布)更快更准确。现已成为所有脚本的默认模式。对于所有你正在维护的代码,优先使用。
--!nonstrict--!strictBasic Type Annotations
基础类型注解
luau
-- Variable annotations
local name: string = "Alice"
local health: number = 100
local isAlive: boolean = true
local data: any = nil -- opt out of type checking
-- Function parameter and return types
local function add(a: number, b: number): number
return a + b
end
-- Optional parameters
local function greet(name: string, title: string?): string
if title then
return `{title} {name}`
end
return name
endluau
-- 变量注解
local name: string = "Alice"
local health: number = 100
local isAlive: boolean = true
local data: any = nil -- 退出类型检查
-- 函数参数与返回类型
local function add(a: number, b: number): number
return a + b
end
-- 可选参数
local function greet(name: string, title: string?): string
if title then
return `{title} {name}`
end
return name
endTable Types
表类型
luau
-- Array type
local scores: { number } = { 100, 95, 87 }
-- Dictionary type (indexer)
local config: { [string]: boolean } = {
shadows = true,
particles = false,
}
-- Record type (concrete fields)
type PlayerData = {
name: string,
level: number,
inventory: { string },
stats: {
health: number,
mana: number,
},
}
local player: PlayerData = {
name = "Alice",
level = 10,
inventory = { "sword", "shield" },
stats = {
health = 100,
mana = 50,
},
}luau
-- 数组类型
local scores: { number } = { 100, 95, 87 }
-- 字典类型(索引器)
local config: { [string]: boolean } = {
shadows = true,
particles = false,
}
-- 记录类型(具体字段)
type PlayerData = {
name: string,
level: number,
inventory: { string },
stats: {
health: number,
mana: number,
},
}
local player: PlayerData = {
name = "Alice",
level = 10,
inventory = { "sword", "shield" },
stats = {
health = 100,
mana = 50,
},
}Sealed vs Unsealed Tables
密封表与非密封表
luau
-- UNSEALED: unannotated local tables accumulate fields
local config = {}
config.debug = true -- fine, table is unsealed
config.version = "1.0" -- fine, still accumulating
-- SEALED: once annotated or returned, no new fields allowed
local settings: { debug: boolean } = { debug = true }
settings.version = "1.0" -- ERROR: 'version' not in type
-- Practical implication: build tables fully before annotating
local data = {
name = "Alice",
level = 10,
}
-- data is unsealed here, you can still add fields
data.guild = "Warriors"
-- But once you pass it to a typed function or return it, it sealsluau
-- 非密封:未注解的局部表会累积字段
local config = {}
config.debug = true -- 合法,表为非密封状态
config.version = "1.0" -- 合法,仍可累积字段
-- 密封:一旦注解或被返回,无法添加新字段
local settings: { debug: boolean } = { debug = true }
settings.version = "1.0" -- 错误:类型中不存在'version'
-- 实际应用:在添加注解前完成表的构建
local data = {
name = "Alice",
level = 10,
}
-- 此时data是非密封的,你仍可添加字段
data.guild = "Warriors"
-- 但一旦将其传递给带类型的函数或返回,它就会变成密封状态Union and Intersection Types
联合类型与交叉类型
luau
-- Union type: value can be one of several types
local id: string | number = "abc123"
id = 42 -- also valid
-- Optional is shorthand for T | nil
local nickname: string? = nil -- equivalent to string | nil
-- Useful for function returns that may fail
local function findPlayer(name: string): Player?
-- ...
return nil
end
-- Tagged unions for state machines (discriminated unions)
type Loading = { kind: "loading" }
type Ready<T> = { kind: "ready", value: T }
type Failed = { kind: "failed", message: string }
type State<T> = Loading | Ready<T> | Failed
local function readValue(state: State<number>): number?
if state.kind == "ready" then
return state.value -- narrowed to Ready<number>
end
return nil
endluau
-- 联合类型:值可以是多种类型之一
local id: string | number = "abc123"
id = 42 -- 同样合法
-- 可选类型是T | nil的简写
local nickname: string? = nil -- 等价于string | nil
-- 适用于可能执行失败的函数返回值
local function findPlayer(name: string): Player?
-- ...
return nil
end
-- 用于状态机的标记联合类型(可区分联合类型)
type Loading = { kind: "loading" }
type Ready<T> = { kind: "ready", value: T }
type Failed = { kind: "failed", message: string }
type State<T> = Loading | Ready<T> | Failed
local function readValue(state: State<number>): number?
if state.kind == "ready" then
return state.value -- 类型收窄为Ready<number>
end
return nil
endType Narrowing and Guards
类型收窄与守卫
luau
-- typeof narrows types (Roblox-aware, preferred over type())
local function process(value: string | number)
if typeof(value) == "string" then
-- value is narrowed to string here
print(string.upper(value))
else
-- value is narrowed to number here
print(value * 2)
end
end
-- Instance type checking with :IsA()
local function handlePart(instance: Instance)
if instance:IsA("BasePart") then
-- instance is narrowed to BasePart
instance.Anchored = true
instance.BrickColor = BrickColor.new("Bright red")
end
end
-- assert for non-nil narrowing
local function getPlayerData(player: Player): PlayerData
local leaderstats = player:FindFirstChild("leaderstats")
assert(leaderstats, "Player missing leaderstats")
-- leaderstats is now narrowed to non-nil
return parseStats(leaderstats)
end
-- Pattern: type guard function
local function isWeapon(item: Item): boolean
return item.category == "weapon"
endluau
-- typeof用于收窄类型(支持Roblox类型,优先于type())
local function process(value: string | number)
if typeof(value) == "string" then
-- 此处value的类型已收窄为string
print(string.upper(value))
else
-- 此处value的类型已收窄为number
print(value * 2)
end
end
-- 使用:IsA()检查Instance类型
local function handlePart(instance: Instance)
if instance:IsA("BasePart") then
-- instance的类型已收窄为BasePart
instance.Anchored = true
instance.BrickColor = BrickColor.new("Bright red")
end
end
-- 使用assert收窄非空类型
local function getPlayerData(player: Player): PlayerData
local leaderstats = player:FindFirstChild("leaderstats")
assert(leaderstats, "Player missing leaderstats")
-- leaderstats现在被收窄为非空类型
return parseStats(leaderstats)
end
-- 模式:类型守卫函数
local function isWeapon(item: Item): boolean
return item.category == "weapon"
endGenerics
泛型
luau
-- Generic function: preserves element type through transforms
local function first<T>(list: { T }): T?
return list[1]
end
local name = first({ "Alice", "Bob" }) -- inferred as string?
local num = first({ 1, 2, 3 }) -- inferred as number?
-- Generic type alias
type Result<T> = {
success: boolean,
value: T?,
error: string?,
}
local function fetchData(): Result<PlayerData>
return {
success = true,
value = { name = "Alice", level = 10, inventory = {}, stats = { health = 100, mana = 50 } },
error = nil,
}
end
-- Generic class-like pattern
type Stack<T> = {
items: { T },
push: (self: Stack<T>, value: T) -> (),
pop: (self: Stack<T>) -> T?,
peek: (self: Stack<T>) -> T?,
}
-- NOTE: In type definitions, self is explicit (it's a function signature).
-- In actual method definitions, use : to hide self (see roblox-luau-patterns).luau
-- 泛型函数:在转换过程中保留元素类型
local function first<T>(list: { T }): T?
return list[1]
end
local name = first({ "Alice", "Bob" }) -- 推断为string?
local num = first({ 1, 2, 3 }) -- 推断为number?
-- 泛型类型别名
type Result<T> = {
success: boolean,
value: T?,
error: string?,
}
local function fetchData(): Result<PlayerData>
return {
success = true,
value = { name = "Alice", level = 10, inventory = {}, stats = { health = 100, mana = 50 } },
error = nil,
}
end
-- 泛型类风格模式
type Stack<T> = {
items: { T },
push: (self: Stack<T>, value: T) -> (),
pop: (self: Stack<T>) -> T?,
peek: (self: Stack<T>) -> T?,
}
-- 注意:在类型定义中,self是显式的(它是函数签名的一部分)。
-- 在实际方法定义中,使用:来隐藏self(详见roblox-luau-patterns)。When to Use Generics
泛型的适用场景
- Yes: When a function transforms input and the output type depends on the input type
- Yes: When a container holds items of a specific type that callers should know about
- Yes: When you want to preserve type relationships across a chain of operations
- No: When the type is always the same (just use the concrete type)
- No: When you'd end up with everywhere (you've lost the benefit)
<any>
- 适用:当函数转换输入且输出类型依赖于输入类型时
- 适用:当容器存储特定类型的元素且调用者需要知晓该类型时
- 适用:当你需要在一系列操作中保留类型关联时
- 不适用:当类型始终相同时(直接使用具体类型即可)
- 不适用:当最终到处都是时(你已失去泛型的价值)
<any>
Type Exports
类型导出
luau
-- In a ModuleScript, export types for other modules to use
-- File: ReplicatedStorage/Types.lua
export type WeaponData = {
name: string,
damage: number,
rarity: "Common" | "Rare" | "Epic" | "Legendary",
durability: number,
}
export type InventorySlot = {
item: WeaponData?,
quantity: number,
}
-- Consumers import with require
-- File: ServerScriptService/WeaponService.lua
local Types = require(game.ReplicatedStorage.Types)
local function createWeapon(name: string, damage: number): Types.WeaponData
return {
name = name,
damage = damage,
rarity = "Common",
durability = 100,
}
endluau
-- 在ModuleScript中,导出类型供其他模块使用
-- 文件:ReplicatedStorage/Types.lua
export type WeaponData = {
name: string,
damage: number,
rarity: "Common" | "Rare" | "Epic" | "Legendary",
durability: number,
}
export type InventorySlot = {
item: WeaponData?,
quantity: number,
}
-- 使用者通过require导入
-- 文件:ServerScriptService/WeaponService.lua
local Types = require(game.ReplicatedStorage.Types)
local function createWeapon(name: string, damage: number): Types.WeaponData
return {
name = name,
damage = damage,
rarity = "Common",
durability = 100,
}
endExport Philosophy
导出理念
- Export named aliases for every type that crosses a module boundary
- Keep implementation types internal (don't export helper types only used inside)
- Choose signatures that let callers infer types cleanly without needing to import the alias
- A well-typed module surface acts as documentation
- 为所有跨模块边界的类型导出命名别名
- 保留内部实现类型(不要仅导出模块内部使用的辅助类型)
- 选择能让调用者无需导入别名即可清晰推断类型的签名
- 类型完善的模块表面可作为文档使用
Common Roblox Types
常见Roblox类型
luau
-- Instance hierarchy types
local part: Part = Instance.new("Part")
local model: Model = Instance.new("Model")
local player: Player = game.Players.LocalPlayer
local character: Model = player.Character or player.CharacterAdded:Wait()
local humanoid: Humanoid = character:FindFirstChildWhichIsA("Humanoid") :: Humanoid
-- Value types (these are NOT instances - they are value types / structs)
local position: Vector3 = Vector3.new(10, 5, 0)
local rotation: CFrame = CFrame.new(0, 10, 0) * CFrame.Angles(0, math.rad(90), 0)
local color: Color3 = Color3.fromRGB(255, 0, 0)
local size: Vector2 = Vector2.new(100, 50)
local region: Region3 = Region3.new(Vector3.new(-10, 0, -10), Vector3.new(10, 20, 10))
local ray: Ray = Ray.new(Vector3.new(0, 10, 0), Vector3.new(0, -1, 0))
local udim2: UDim2 = UDim2.new(0.5, 0, 0.5, 0)
-- Enum types
local material: Enum.Material = Enum.Material.Grass
local partType: Enum.PartType = Enum.PartType.Ballluau
-- Instance层级类型
local part: Part = Instance.new("Part")
local model: Model = Instance.new("Model")
local player: Player = game.Players.LocalPlayer
local character: Model = player.Character or player.CharacterAdded:Wait()
local humanoid: Humanoid = character:FindFirstChildWhichIsA("Humanoid") :: Humanoid
-- 值类型(这些不是Instance - 它们是值类型/结构体)
local position: Vector3 = Vector3.new(10, 5, 0)
local rotation: CFrame = CFrame.new(0, 10, 0) * CFrame.Angles(0, math.rad(90), 0)
local color: Color3 = Color3.fromRGB(255, 0, 0)
local size: Vector2 = Vector2.new(100, 50)
local region: Region3 = Region3.new(Vector3.new(-10, 0, -10), Vector3.new(10, 20, 10))
local ray: Ray = Ray.new(Vector3.new(0, 10, 0), Vector3.new(0, -1, 0))
local udim2: UDim2 = UDim2.new(0.5, 0, 0.5, 0)
-- 枚举类型
local material: Enum.Material = Enum.Material.Grass
local partType: Enum.PartType = Enum.PartType.BallTyping Object-Like Modules
为类对象模块添加类型
luau
--!strict
-- Derive instance type from setmetatable for precise self typing
local Counter = {}
Counter.__index = Counter
type CounterData = { value: number }
export type Counter = typeof(setmetatable({} :: CounterData, Counter))
function Counter.new(initialValue: number): Counter
return setmetatable({ value = initialValue }, Counter)
end
-- Explicit self annotation when : syntax doesn't infer precisely enough
function Counter.increment(self: Counter, amount: number): number
self.value += amount
return self.value
end
return Counterluau
--!strict
-- 从setmetatable派生实例类型以实现精确的self类型
local Counter = {}
Counter.__index = Counter
type CounterData = { value: number }
export type Counter = typeof(setmetatable({} :: CounterData, Counter))
function Counter.new(initialValue: number): Counter
return setmetatable({ value = initialValue }, Counter)
end
-- 当:语法无法精确推断self时,添加显式self注解
function Counter.increment(self: Counter, amount: number): number
self.value += amount
return self.value
end
return CounterWhen to Use Explicit self
self何时使用显式self
self- When the type checker can't infer precisely through
selfsyntax: - When you need to be a specific subtype in an inheritance chain
self - When the method is defined with but called with
.(rare, avoid if possible): - In type definitions (function signatures in type aliases always need explicit self)
- 当类型检查器无法通过语法精确推断
:时self - 当你需要成为继承链中的特定子类型时
self - 当方法用定义但用
.调用时(罕见,尽可能避免): - 在类型定义中(类型别名中的函数签名始终需要显式self)
Common Mistakes
常见错误
- Leaving variables unannotated in → unintentional
--!nonstrictpropagationany - Replacing useful generic relationships with or overly broad unions
any - Sealing a table too early with an annotation, then expecting to add fields later
- Expecting method definitions to automatically share precise
:type across the classself - Using to force unrelated conversions instead of fixing underlying type design
:: - Building unions without a discriminant, making downstream refinement difficult
- Using intersections between incompatible primitives ()
string & number - Annotating every local variable (noise that hides the important annotations)
- Exporting internal helper types that clutter the module's public surface
- 在中保留未注解的变量 → 意外的
--!nonstrict类型传播any - 用或过于宽泛的联合类型替代有用的泛型关联
any - 过早用注解密封表,之后又想添加字段
- 期望方法定义能自动在类中共享精确的
:类型self - 使用强制无关类型转换,而非修复底层类型设计
:: - 创建无判别字段的联合类型,导致下游细化操作困难
- 在不兼容的原始类型间使用交叉类型(如)
string & number - 为每个局部变量添加注解(噪音会掩盖重要注解)
- 导出内部辅助类型,导致模块公共表面杂乱
Quality Checklist
质量检查清单
- File has appropriate strictness mode (for maintained code)
--!strict - Function parameters and return types annotated at module boundaries
- Internal locals rely on inference where the inferred type is precise
- Generics preserve type relationships (no escape hatches)
any - Tagged unions have a discriminant field for narrowing
- Exported types are named, focused, and documented
- Casts () are justified (narrowing, not hiding errors)
:: - No sealed table violations (fields added after annotation)
- 文件使用了合适的严格性模式(维护中的代码使用)
--!strict - 模块边界处的函数参数和返回类型已注解
- 内部局部变量在推断类型精确时依赖类型推断
- 泛型保留了类型关联(无规避手段)
any - 标记联合类型有用于收窄的判别字段
- 导出的类型命名清晰、聚焦且有文档说明
- 类型转换()有合理理由(用于收窄,而非隐藏错误)
:: - 无密封表违规(注解后添加字段)