mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-10-23 22:11:38 +00:00
- Updated the Luau rules documentation to clarify the default context as Roblox Luau and provided detailed paths for accessing Roblox-specific and official Luau documentation. - Added new sections on string literal escapes, number literals, primitive types, and type refinements, including examples to illustrate usage. - Improved explanations of sealed vs unsealed tables and user-defined type functions, enhancing clarity for developers. These changes aim to improve the comprehensiveness and usability of the Luau documentation, facilitating better understanding and application of the language features.
809 lines
22 KiB
Text
809 lines
22 KiB
Text
---
|
|
alwaysApply: true
|
|
---
|
|
# Luau Language Rules
|
|
|
|
**Default context: Roblox Luau.** Unless otherwise stated, assume we are programming in Roblox Studio. Standard Luau differences are noted where relevant.
|
|
|
|
## Finding Documentation
|
|
|
|
- `.cursor/rules/roblox/en-us/luau/` - Roblox-specific Luau documentation (types, Roblox classes/enums, Studio integration)
|
|
- `.cursor/rules/luau/` - Official Luau language documentation (type system, standard library, syntax, guides)
|
|
- `.cursor/rules/luau-rfcs/` - RFC documents detailing every language feature and design decision
|
|
|
|
## Single-Threaded Execution Model
|
|
|
|
**Luau is completely single-threaded.** This is the most misunderstood aspect of the language.
|
|
|
|
### Key Facts
|
|
|
|
- Code in one script **cannot** run simultaneously with another script
|
|
- Execution only switches when code **yields** (`task.wait()`, `:Wait()`, `coroutine.yield()`, async APIs)
|
|
- **No yield = no other code runs.** Infinite loops without yields freeze the game.
|
|
|
|
### Practical Implications
|
|
|
|
```luau
|
|
-- SAFE: No yield between read/write, guaranteed atomic
|
|
local value = data.Value
|
|
data.Value = value + 1
|
|
|
|
-- UNSAFE: Yield allows other code to modify data.Value
|
|
local value = data.Value
|
|
task.wait(0.1) -- Other code can run here!
|
|
data.Value = value + 1 -- May not be what you expect
|
|
```
|
|
|
|
### Common Misconceptions
|
|
|
|
1. **"Multiple scripts run in parallel"** - False. They switch only at yield points.
|
|
2. **"I need mutexes/locks"** - Not in pure Luau. No yield = exclusive access.
|
|
3. **"`task.spawn` runs code in parallel"** - False. It schedules for later.
|
|
|
|
## Luau-Specific Syntax
|
|
|
|
### Arrays Are 1-Indexed
|
|
|
|
**Critical:** Unlike most languages, Luau arrays start at index 1, not 0.
|
|
|
|
```luau
|
|
local arr = {"first", "second", "third"}
|
|
print(arr[1]) -- "first" (NOT arr[0])
|
|
print(arr[0]) -- nil
|
|
|
|
for i = 1, #arr do -- Start at 1
|
|
print(arr[i])
|
|
end
|
|
```
|
|
|
|
### Generic Instantiation Uses `<<T>>`
|
|
|
|
This is unique to Luau and easy to forget:
|
|
|
|
```luau
|
|
-- Correct: Double angle brackets
|
|
local binding = React.createBinding<<number?>>()
|
|
local result = someFunc<<string, number>>()
|
|
|
|
-- WRONG: Single angle brackets (won't work)
|
|
-- local binding = React.createBinding<number?>()
|
|
```
|
|
|
|
### Type Ascription Uses `::`
|
|
|
|
```luau
|
|
local x = someValue :: string
|
|
local y = (a + b) :: number
|
|
```
|
|
|
|
### String Interpolation Uses Backticks
|
|
|
|
```luau
|
|
local name = "world"
|
|
print(`Hello {name}!`) -- Hello world!
|
|
print(`Result: {1 + 2}`) -- Result: 3
|
|
|
|
-- Escape with backslash
|
|
print(`Literal braces: \{not interpolated}`)
|
|
```
|
|
|
|
### String Literal Escapes
|
|
|
|
Beyond standard escapes, Luau supports:
|
|
|
|
```luau
|
|
"\xAB" -- Hex escape: inserts byte 0xAB
|
|
"\u{1F600}" -- Unicode escape: inserts UTF-8 for U+1F600 (braces required)
|
|
"long\z
|
|
string" -- \z skips following whitespace/newlines: "longstring"
|
|
```
|
|
|
|
Backtick strings (interpolated) use `\` to escape `` ` ``, `{`, `\`, and newlines. `{{` is a parse error (use `\{` to get a literal brace).
|
|
|
|
### If Expressions (Ternary)
|
|
|
|
```luau
|
|
local x = if condition then value else alternative
|
|
local size = if x > 10 then "big" elseif x > 5 then "medium" else "small"
|
|
```
|
|
|
|
### Continue Statement
|
|
|
|
```luau
|
|
for i = 1, 10 do
|
|
if i % 2 == 0 then continue end
|
|
print(i)
|
|
end
|
|
```
|
|
|
|
### Compound Assignment
|
|
|
|
```luau
|
|
a += b -- a = a + b
|
|
x //= 2 -- Floor division
|
|
str ..= "suffix"
|
|
```
|
|
|
|
### Floor Division
|
|
|
|
```luau
|
|
local half = width // 2 -- More efficient than math.floor(width / 2)
|
|
```
|
|
|
|
### Number Literals
|
|
|
|
Luau has a single number type (64-bit IEEE 754 double, integers exact up to 2^53).
|
|
|
|
```luau
|
|
local hex = 0xFF -- Hexadecimal
|
|
local bin = 0b10101010 -- Binary
|
|
local big = 1_048_576 -- Decimal separators for readability
|
|
local hexSep = 0xFFFF_FFFF
|
|
local binSep = 0b_0101_0101
|
|
```
|
|
|
|
## Type System Essentials
|
|
|
|
### Type Modes
|
|
|
|
- `--!strict`: Full type checking (recommended)
|
|
- `--!nonstrict`: Reduced checking
|
|
- `--!nocheck`: Disabled
|
|
|
|
### Primitive Types
|
|
|
|
The Luau VM has 10 primitive types: `nil`, `string`, `number`, `boolean`, `table`, `function`, `thread`, `userdata`, `vector`, `buffer`.
|
|
|
|
```luau
|
|
local x: number
|
|
local s: string
|
|
local b: boolean
|
|
local co: thread = coroutine.running()
|
|
local buf: buffer = buffer.create(256)
|
|
```
|
|
|
|
Note: `table` and `function` use dedicated syntax (not written by name). `vector` is not representable by name in type annotations. `userdata` is represented by concrete host types.
|
|
|
|
### `unknown`, `any`, and `never`
|
|
|
|
```luau
|
|
local u: unknown = anything -- Top type: accepts all values, but cannot be used without refinement
|
|
local a: any = anything -- Escape hatch: opts out of type checking entirely
|
|
local n: never -- Bottom type: no value inhabits this type
|
|
```
|
|
|
|
- `unknown` requires type refinement (e.g., `typeof(x) == "string"`) before use
|
|
- `any` silently allows all operations -- use sparingly
|
|
- `never` is useful for exhaustiveness checking and impossible branches
|
|
|
|
### Core Type Syntax
|
|
|
|
```luau
|
|
local arr: {string} -- Array shorthand (equivalent to {[number]: string})
|
|
local map: {[string]: number} -- Dictionary with indexer
|
|
local obj: { x: number, y: number } -- Table with named properties
|
|
|
|
local maybe: string? -- Optional: string | nil
|
|
|
|
-- Union / Intersection (leading | and & are allowed for formatting)
|
|
type Result = "ok" | "error"
|
|
type Action
|
|
= { type: "add", value: number }
|
|
| { type: "remove", id: string }
|
|
type Combined = TypeA & TypeB
|
|
|
|
type NotNil = ~nil -- Negation type
|
|
|
|
-- Singleton (literal) types -- strings and booleans only, not numbers
|
|
type Direction = "up" | "down" | "left" | "right"
|
|
type Truthy = true
|
|
|
|
-- Function types with optional named parameters
|
|
type Callback = (number, string) -> boolean
|
|
type Named = (errorCode: number, errorText: string) -> ()
|
|
type Generic = <T>(T) -> T
|
|
type Multi = (number, string) -> (boolean, string) -- Multiple returns
|
|
```
|
|
|
|
### Generics with Defaults
|
|
|
|
Type aliases support default type parameters (functions do not):
|
|
|
|
```luau
|
|
type Pair<T> = { first: T, second: T }
|
|
type PairWithDefault<T = string> = Pair<T>
|
|
|
|
local p: PairWithDefault = { first = "hello", second = "world" } -- T defaults to string
|
|
```
|
|
|
|
### Read-Only Properties
|
|
|
|
```luau
|
|
type ReadOnly = { read x: number }
|
|
type WriteOnly = { write x: number }
|
|
```
|
|
|
|
### Tagged Unions (Discriminated Unions)
|
|
|
|
Common pattern for type-safe variants:
|
|
|
|
```luau
|
|
type Action =
|
|
| { type: "increment" }
|
|
| { type: "decrement" }
|
|
| { type: "set", value: number }
|
|
|
|
function reducer(state: number, action: Action): number
|
|
if action.type == "increment" then
|
|
return state + 1
|
|
elseif action.type == "decrement" then
|
|
return state - 1
|
|
elseif action.type == "set" then
|
|
return action.value
|
|
else
|
|
error(`Unknown action`)
|
|
end
|
|
end
|
|
```
|
|
|
|
### Built-in Type Functions
|
|
|
|
```luau
|
|
type Keys = keyof<MyTable> -- Union of all property names
|
|
type RawKeys = rawkeyof<MyTable> -- Keys without __index
|
|
type PropType = index<Person, "age"> -- Property type lookup
|
|
type RawProp = rawget<Person, "age"> -- Property lookup without __index
|
|
```
|
|
|
|
### Variadic Type Packs
|
|
|
|
For variadic functions, use `...type` in type definitions:
|
|
|
|
```luau
|
|
-- Function definition
|
|
local function addNumbers(...: number)
|
|
-- implementation
|
|
end
|
|
|
|
-- Type definition
|
|
type AddNumbers = (...number) -> number
|
|
|
|
-- Generic type packs (advanced)
|
|
type Fields<self, P..., R...> = {
|
|
call: (self, P...) -> R...
|
|
}
|
|
```
|
|
|
|
### Type Exports
|
|
|
|
Share types across modules with `export type`:
|
|
|
|
```luau
|
|
-- In Types.luau
|
|
export type Player = {
|
|
name: string,
|
|
score: number
|
|
}
|
|
|
|
-- In another module
|
|
local Types = require(ReplicatedStorage.Types)
|
|
local player: Types.Player = {name = "Alice", score = 100}
|
|
```
|
|
|
|
### Inferred Types with `typeof`
|
|
|
|
Use `typeof` to infer types from values:
|
|
|
|
```luau
|
|
local config = {
|
|
maxRetries = 3,
|
|
timeout = 30
|
|
}
|
|
type Config = typeof(config) -- Infers {maxRetries: number, timeout: number}
|
|
|
|
-- Useful for metatable types
|
|
type Vector = typeof(setmetatable({}::{
|
|
x: number,
|
|
y: number
|
|
}, {}::{
|
|
__add: (Vector, Vector) -> Vector
|
|
}))
|
|
```
|
|
|
|
### Type Refinements
|
|
|
|
The type checker narrows types based on control flow:
|
|
|
|
```luau
|
|
local x: string | number = getValue()
|
|
|
|
-- Type guard with type() or typeof()
|
|
if type(x) == "string" then
|
|
local s: string = x -- ok, narrowed to string
|
|
end
|
|
|
|
-- Truthy test
|
|
local maybe: string? = getMaybe()
|
|
if maybe then
|
|
local s: string = maybe -- ok, nil ruled out
|
|
end
|
|
|
|
-- Equality narrowing to singleton
|
|
local s: string = getStr()
|
|
if s == "hello" then
|
|
local h: "hello" = s -- ok, narrowed to literal
|
|
end
|
|
|
|
-- Compose with and/or/not
|
|
if (type(x) == "string" or type(x) == "number") and x ~= nil then
|
|
-- x: string | number
|
|
end
|
|
|
|
-- assert() also refines
|
|
assert(type(x) == "string")
|
|
local s: string = x -- ok after assert
|
|
```
|
|
|
|
### Sealed vs Unsealed Tables
|
|
|
|
Tables have different mutability rules depending on how they are created:
|
|
|
|
**Unsealed** (table literals without annotation) -- can add new properties until scope exits:
|
|
|
|
```luau
|
|
local t = { x = 1 } -- unsealed: {x: number}
|
|
t.y = 2 -- ok, adds y: {x: number, y: number}
|
|
t.z = 3 -- ok
|
|
|
|
local u: { x: number, y: number? } = t -- ok, y is optional
|
|
```
|
|
|
|
**Sealed** (annotated or returned from function) -- locked, supports width subtyping:
|
|
|
|
```luau
|
|
local t: { x: number } = { x = 1 }
|
|
t.y = 2 -- NOT ok, table is sealed
|
|
|
|
type Point2D = { x: number, y: number }
|
|
type Point1D = { x: number }
|
|
local p: Point2D = { x = 5, y = 37 }
|
|
local q: Point1D = p -- ok, Point2D is a subtype (has extra properties)
|
|
```
|
|
|
|
Once an unsealed table leaves its defining scope (e.g., returned from a function), it becomes sealed.
|
|
|
|
### User-Defined Type Functions
|
|
|
|
Type functions run at analysis time and operate on types using the `types` library:
|
|
|
|
```luau
|
|
type function simple_keyof(ty)
|
|
if not ty:is("table") then
|
|
error("Can only call keyof on tables.")
|
|
end
|
|
|
|
local union = nil
|
|
for property in ty:properties() do
|
|
union = if union then types.unionof(union, property) else property
|
|
end
|
|
return if union then union else types.singleton(nil)
|
|
end
|
|
|
|
type Person = { name: string, age: number }
|
|
type Keys = simple_keyof<Person> -- "age" | "name"
|
|
```
|
|
|
|
Available in type function environment: `types` library, `assert`, `error`, `print`, `next`, `pairs`, `ipairs`, `select`, `unpack`, `tostring`, `tonumber`, `type`, `typeof`, `math`, `table`, `string`, `bit32`, `utf8`, `buffer` libraries.
|
|
|
|
## Roblox-Specific
|
|
|
|
For full Roblox Luau documentation, see `.cursor/rules/roblox/en-us/luau/`.
|
|
|
|
### Roblox Types in Annotations
|
|
|
|
All Roblox classes, data types, and enums are valid type annotations:
|
|
|
|
```luau
|
|
local part: Part = Instance.new("Part")
|
|
local color: BrickColor = part.BrickColor
|
|
local material: Enum.Material = part.Material
|
|
local pos: Vector3 = part.Position
|
|
local cf: CFrame = part.CFrame
|
|
```
|
|
|
|
### Use Vector3, Not vector
|
|
|
|
The `vector` primitive exists but is not compatible with Roblox APIs. Always use `Vector3`:
|
|
|
|
```luau
|
|
local pos = Vector3.new(1, 2, 3)
|
|
-- NOT: vector.create(1, 2, 3)
|
|
```
|
|
|
|
### Native Code Generation (Server Only)
|
|
|
|
In Roblox, `--!native` compiles **server-side scripts** to machine code. There is a limit on total natively compiled code per experience. Use `@native` on individual hot functions to stay within budget.
|
|
|
|
## Module Require Semantics
|
|
|
|
### Roblox Luau (Default)
|
|
|
|
In Roblox, use instance-path requires. This is the preferred style:
|
|
|
|
```luau
|
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
local MyModule = require(ReplicatedStorage.Modules.MyModule)
|
|
local Types = require(ReplicatedStorage.Shared.Types)
|
|
|
|
-- Common pattern: services at top of file
|
|
local Players = game:GetService("Players")
|
|
local RunService = game:GetService("RunService")
|
|
local ServerStorage = game:GetService("ServerStorage")
|
|
```
|
|
|
|
### Standalone Luau (String Requires)
|
|
|
|
Outside Roblox (e.g., Lune, standalone tooling), requires use explicit string paths with mandatory prefixes:
|
|
|
|
```luau
|
|
local sibling = require("./sibling") -- Relative to current file
|
|
local parent = require("../parent") -- Parent directory
|
|
local lib = require("@lune/net") -- Alias (configured in .luaurc)
|
|
```
|
|
|
|
All paths must start with `./`, `../`, or `@` -- bare names like `require("foo")` are not allowed. Aliases are configured in `.luaurc`:
|
|
|
|
```json
|
|
{
|
|
"aliases": {
|
|
"lune": "packages/lune",
|
|
"shared": "src/shared"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Cyclic Dependencies
|
|
|
|
Require paths must be statically resolvable. To break cyclic module dependencies, cast to `any`:
|
|
|
|
```luau
|
|
local myModule = require(MyModule) :: any
|
|
```
|
|
|
|
## Deprecated Functions
|
|
|
|
**Never use these:**
|
|
|
|
- `getfenv`/`setfenv`: Breaks optimizations and type checking
|
|
- `table.getn`: Use `#` or `rawlen`
|
|
- `table.foreach`/`table.foreachi`: Use `for..in` loops
|
|
- `wait()`: Use `task.wait()`
|
|
|
|
## Common Gotchas
|
|
|
|
### Raw Table Functions
|
|
|
|
When working with metatables, use `rawget`/`rawset`/`rawequal` to bypass metamethods:
|
|
|
|
```luau
|
|
local t = setmetatable({}, {
|
|
__index = function(self, key)
|
|
rawset(self, key, key * 10) -- Avoid triggering __newindex
|
|
return self[key]
|
|
end
|
|
})
|
|
|
|
-- rawget: Get without triggering __index
|
|
local val = rawget(t, "key")
|
|
|
|
-- rawset: Set without triggering __newindex
|
|
rawset(t, "key", "value")
|
|
|
|
-- rawequal: Compare without triggering __eq
|
|
if rawequal(a, b) then end
|
|
```
|
|
|
|
### Metamethod Differences from Lua
|
|
|
|
- `__eq` always called, even for rawequal objects
|
|
- `__len` works on tables (not just userdata)
|
|
- `__iter` enables custom iteration
|
|
|
|
### No Bitwise Shift Operators
|
|
|
|
`<<` and `>>` don't exist (conflicts with generic syntax). Use `bit32.lshift`/`bit32.rshift`.
|
|
|
|
### Table Iteration
|
|
|
|
Tables can be iterated directly without `pairs`/`ipairs`:
|
|
|
|
```luau
|
|
for k, v in myTable do
|
|
-- Works directly
|
|
end
|
|
```
|
|
|
|
### Spreading Arrays with `unpack`
|
|
|
|
Use `unpack()` to spread array values:
|
|
|
|
```luau
|
|
local args = {1, 2, 3}
|
|
print(unpack(args)) -- 1 2 3
|
|
|
|
local function add(a, b, c)
|
|
return a + b + c
|
|
end
|
|
print(add(unpack(args))) -- 6
|
|
```
|
|
|
|
### Function Attributes
|
|
|
|
```luau
|
|
@native
|
|
function hotFunction()
|
|
-- Compiled to native code
|
|
end
|
|
|
|
@deprecated
|
|
function oldFunction()
|
|
end
|
|
```
|
|
|
|
Note: `@inline` is NOT supported. Compiler handles inlining automatically.
|
|
|
|
## OOP Pattern
|
|
|
|
Separate the data type from the class type using `typeof(setmetatable(...))`. Annotate `self` explicitly on methods -- this is **required** for the type checker to share `self` types across methods.
|
|
|
|
```luau
|
|
local Point = {}
|
|
Point.__index = Point
|
|
|
|
type PointData = {
|
|
x: number,
|
|
y: number,
|
|
}
|
|
|
|
export type Point = typeof(setmetatable({} :: PointData, Point))
|
|
|
|
function Point.new(x: number, y: number): Point
|
|
return setmetatable({ x = x, y = y }, Point)
|
|
end
|
|
|
|
function Point.move(self: Point, dx: number, dy: number)
|
|
self.x += dx
|
|
self.y += dy
|
|
end
|
|
|
|
function Point.magnitude(self: Point): number
|
|
return math.sqrt(self.x * self.x + self.y * self.y)
|
|
end
|
|
|
|
local p = Point.new(3, 4)
|
|
p:move(1, 2) -- Callers still use : syntax
|
|
print(p:magnitude()) -- 5.385...
|
|
```
|
|
|
|
Key points:
|
|
- Define methods with `.` syntax and explicit `self: ClassName` parameter
|
|
- Callers still invoke with `:` syntax
|
|
- Return type annotation on the constructor ensures compatibility with methods
|
|
- The `typeof(setmetatable(...))` trick gives full autocomplete on `self`
|
|
|
|
## Performance Tips
|
|
|
|
1. **Preallocate tables**: `table.create(count)` for known sizes
|
|
2. **Reuse tables**: `table.clear()` instead of creating new
|
|
3. **Floor division**: `//` is faster than `math.floor(a / b)`
|
|
4. **Avoid yields in hot paths**: Remember the single-threaded model
|
|
|
|
### Native Code Generation
|
|
|
|
Use `--!native` to compile server scripts to machine code for ~2-10x speedup:
|
|
|
|
```luau
|
|
--!native
|
|
--!strict
|
|
|
|
-- Best for: computation-heavy code, math operations, buffer manipulation
|
|
-- Avoid: getfenv/setfenv, non-numeric args to math functions
|
|
```
|
|
|
|
Or use `@native` for individual functions:
|
|
|
|
```luau
|
|
@native
|
|
local function calculatePhysics(dt: number)
|
|
-- Heavy computation here
|
|
end
|
|
```
|
|
|
|
**When to use:**
|
|
- Functions with heavy math/loops called frequently
|
|
- `buffer` operations
|
|
- Avoid for API-heavy code (gains are minimal)
|
|
|
|
**Annotate `Vector3` parameters** for best results:
|
|
|
|
```luau
|
|
-- Slow: "v" assumed to be table
|
|
local function magnitude(v)
|
|
return math.sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z)
|
|
end
|
|
|
|
-- Fast: compiler generates optimized vector code
|
|
local function magnitude(v: Vector3)
|
|
return math.sqrt(v.X * v.X + v.Y * v.Y + v.Z * v.Z)
|
|
end
|
|
```
|
|
|
|
## Module Exports with Types
|
|
|
|
```luau
|
|
--!strict
|
|
|
|
export type MyType = { field: string }
|
|
|
|
local Module = {}
|
|
|
|
function Module.create(): MyType
|
|
return { field = "value" }
|
|
end
|
|
|
|
return Module
|
|
```
|
|
|
|
## Exhaustiveness Checking
|
|
|
|
Use `never` type to ensure all cases are handled:
|
|
|
|
```luau
|
|
function handle(x: "a" | "b")
|
|
if x == "a" then
|
|
return 1
|
|
elseif x == "b" then
|
|
return 2
|
|
else
|
|
local _: never = x -- Type error if not exhaustive
|
|
end
|
|
end
|
|
```
|
|
|
|
## Standard Library Additions
|
|
|
|
Luau extends Lua 5.1's standard library with many functions. Below are the most important additions not covered elsewhere.
|
|
|
|
### Math Library Extensions
|
|
|
|
```luau
|
|
math.clamp(n, min, max) -- Clamp n to [min, max] range
|
|
math.sign(n) -- Returns -1, 0, or 1
|
|
math.round(n) -- Round to nearest integer (halfway rounds away from 0)
|
|
math.lerp(a, b, t) -- Linear interpolation: a + (b - a) * t, exact at t=1
|
|
math.map(x, inmin, inmax, outmin, outmax) -- Map x from input range to output range
|
|
math.noise(x, y?, z?) -- 3D Perlin noise, returns [-1, 1]
|
|
```
|
|
|
|
Also available: `math.log(n, base?)` (optional base), `math.random()` uses PCG RNG.
|
|
|
|
### String Library Extensions
|
|
|
|
```luau
|
|
-- string.split: splits string into array (default separator: ",")
|
|
local parts = string.split("a,b,c", ",") -- {"a", "b", "c"}
|
|
local chars = string.split("hello", "") -- {"h", "e", "l", "l", "o"}
|
|
|
|
-- string.pack/unpack/packsize: binary data encoding (Lua 5.3 format strings)
|
|
-- Fixed sizes: short=16bit, long=64bit, int=32bit, size_t=32bit
|
|
local packed = string.pack("i4f", 42, 3.14)
|
|
local int, float = string.unpack("i4f", packed)
|
|
local size = string.packsize("i4f") -- 8 bytes
|
|
```
|
|
|
|
### bit32 Library Extensions
|
|
|
|
```luau
|
|
bit32.countlz(n) -- Count leading zeros (returns 32 if n is 0)
|
|
bit32.countrz(n) -- Count trailing zeros (returns 32 if n is 0)
|
|
bit32.byteswap(n) -- Swap byte order (endianness)
|
|
```
|
|
|
|
### coroutine.close
|
|
|
|
```luau
|
|
coroutine.close(co) -- Puts suspended/dead coroutine into dead state, frees stack
|
|
-- Returns (true) on success, (false, error) if coroutine was in error state
|
|
```
|
|
|
|
## Immutable Tables
|
|
|
|
```luau
|
|
local config = table.freeze({
|
|
maxRetries = 3,
|
|
timeout = 30
|
|
})
|
|
|
|
-- Clone before modifying
|
|
local newConfig = table.clone(config)
|
|
newConfig.maxRetries = 5
|
|
```
|
|
|
|
## Buffer Library
|
|
|
|
Buffers are fixed-size mutable byte arrays. All offsets are 0-based. Reads/writes outside bounds throw errors. Uses little-endian byte order.
|
|
|
|
```luau
|
|
local buf = buffer.create(1024) -- All bytes initialized to 0
|
|
local buf2 = buffer.fromstring("hello") -- From string contents
|
|
local str = buffer.tostring(buf2) -- Back to string
|
|
local len = buffer.len(buf) -- Size in bytes
|
|
|
|
-- Typed reads/writes (i=signed, u=unsigned, f=float)
|
|
buffer.writei32(buf, 0, 42) -- Write signed 32-bit int at offset 0
|
|
local n = buffer.readi32(buf, 0) -- Read it back
|
|
buffer.writef64(buf, 4, 3.14159) -- Write 64-bit float at offset 4
|
|
local f = buffer.readf64(buf, 4)
|
|
|
|
-- Available: readi8/u8/i16/u16/i32/u32/f32/f64 and matching write variants
|
|
|
|
-- String read/write
|
|
buffer.writestring(buf, 0, "hello", 5) -- Write 5 bytes from string
|
|
local s = buffer.readstring(buf, 0, 5) -- Read 5 bytes as string
|
|
|
|
-- Bit-level access
|
|
buffer.writebits(buf, 0, 4, 0b1010) -- Write 4 bits at bit offset 0
|
|
local bits = buffer.readbits(buf, 0, 4) -- Read 4 bits (returns unsigned int)
|
|
|
|
-- Bulk operations
|
|
buffer.copy(target, tOff, source, sOff?, count?) -- Copy between buffers (or within same)
|
|
buffer.fill(buf, offset, value, count?) -- Fill bytes with value
|
|
```
|
|
|
|
## Vector Library
|
|
|
|
The `vector` primitive is a 3-component (or 4-component in 4-wide mode) immutable value type. Components are accessed via `.x`/`.y`/`.z` (and `.w` in 4-wide mode) but cannot be written individually.
|
|
|
|
```luau
|
|
-- Construction and constants
|
|
local v = vector.create(1, 2, 3)
|
|
local zero = vector.zero -- (0, 0, 0)
|
|
local one = vector.one -- (1, 1, 1)
|
|
|
|
-- Arithmetic: +, -, *, /, //, unary - all work component-wise
|
|
local sum = v + vector.create(4, 5, 6)
|
|
|
|
-- Geometric operations
|
|
vector.magnitude(v) -- Length of vector
|
|
vector.normalize(v) -- Unit vector
|
|
vector.dot(v1, v2) -- Dot product (returns number)
|
|
vector.cross(v1, v2) -- Cross product (3D only)
|
|
vector.angle(v1, v2, axis?) -- Angle in radians (axis for signed angle)
|
|
vector.lerp(v1, v2, t) -- Linear interpolation
|
|
|
|
-- Component-wise math
|
|
vector.abs(v) -- math.abs per component
|
|
vector.sign(v) -- math.sign per component
|
|
vector.floor(v) / vector.ceil(v) -- Rounding per component
|
|
vector.clamp(v, min, max) -- Clamp per component
|
|
vector.max(v1, v2, ...) / vector.min(v1, v2, ...)
|
|
```
|
|
|
|
**Note:** In Roblox, prefer `Vector3` over `vector` -- `Vector3` integrates with all Roblox APIs. The `vector` library is for standalone Luau or performance-critical native code.
|
|
|
|
## Differences from Lua
|
|
|
|
Luau is based on Lua 5.1 with selective features from 5.2-5.4 and its own additions:
|
|
|
|
- `_VERSION` is `"Luau"` (not `"Lua 5.1"`)
|
|
- Single number type: 64-bit double (no separate integer type)
|
|
- No tail call optimization (simplifies debugging and stack traces)
|
|
- `__eq` metamethod is always called, even when objects are rawequal
|
|
- No `goto` statement
|
|
- No bitwise operators (`<<`, `>>`, `&`, `|`, `~`) -- use `bit32` library
|
|
- No `__gc` metamethod (sandboxing constraint)
|
|
- No `io`, `os.execute`, `package`, or full `debug` library (sandboxing)
|
|
- `loadstring` cannot load bytecode, `string.dump` not available
|
|
- Table assignment order in literals follows program order (Lua 5.x may reorder)
|
|
- `function()` expressions may reuse closures when all captured upvalues are identical
|
|
- `os.time` with table argument returns UTC (not local time)
|