Destructuring
Destructuring pulls several fields out of a table in one line.
In Luau, you write each name twice:
const player = { name = "Ada", level = 12, class = "Mage" }
local name = player.name
local level = player.level
print(name, level)In Luwu, you list the names once:
const player = { name = "Ada", level = 12, class = "Mage" }
const .{ name, level } = player
print(name, level).{ name, level } reads player.name and player.level. The table is evaluated once.
Renaming with as
Use as to give a field a different local name. The field's name comes first.
const response = { status = 200, body = "ok" }
const .{ status as code, body as text } = response
print(code, text)Nested tables
Patterns nest. Add a dot and braces after a name to reach inside it.
const user = {
name = "Ada",
address = { city = "London", country = "UK" },
}
const .{ name, address.{ city } } = user
print(name, city, address.country)address.{ city } binds both address and city. To skip address, rename it into a pattern:
const user = { address = { city = "London", country = "UK" } }
const .{ address as .{ city, country } } = user
print(city, country)Keeping the whole value
Put a name before the braces to bind the whole table too.
const function load_config()
return { width = 1280, height = 720, title = "Game" }
end
const config.{ width, height } = load_config()
print(width * height, config.title)Imports
At the top of a file, destructuring takes only what you need from a module.
const .{ readfile, writefile } = require("@std/fs")
const fs.{ path } = require("@std/fs")
print(typeof(readfile), typeof(writefile), typeof(path.join), typeof(fs))type .{ ... } = module does the same for a module's exported types:
const process = require("@std/process")
type .{ RunResult } = process
const function succeeded(result: RunResult): boolean
return result.ok
end
print(typeof(succeeded))Loops
Destructure each item in a for loop's header.
const books = {
{ title = "Dune", year = 1965 },
{ title = "Hyperion", year = 1989 },
}
for _, .{ title, year } in books do
print(`{title} came out in {year}`)
endIn if local
A destructuring binding in an if local passes when every name it binds is truthy.
const function lookup(id: number): { name: string?, email: string? }
return if id == 1 then { name = "Ada", email = "ada@example.com" } else {}
end
if const .{ name, email } = lookup(1) then
print(`Mail {name} at {email}`)
endBuilding tables with .{ }
The same braces work the other way around. A bare name becomes a field of the same name.
const name = "Ada"
const email = "ada@example.com"
const user = .{ name, email, verified = true }
print(user.name, user.email, user.verified)It's handy for returning several values by name:
const function stats(numbers: { number })
local min, max, sum = math.huge, -math.huge, 0
for _, n in numbers do
min = math.min(min, n)
max = math.max(max, n)
sum += n
end
const average = sum / #numbers
return .{ min, max, average }
end
const .{ min, max, average } = stats({ 4, 8, 15, 16, 23, 42 })
print(min, max, average)It also builds plain old data (POD) classes:
class Color
r = 0
g = 0
b = 0
end
const r, g, b = 255, 128, 0
const orange = Color.{ r, g, b }
print(orange.r, orange.g, orange.b)Things to know
- Destructuring reads fields by name. A missing field on a plain table gives nil.
- On a class object, a missing field is an error, like any other read.
- Function parameters can't be destructured. Destructure inside the function instead.
- You can't assign to a pattern.
.{ a, b } = twithout const or local is an error.