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}`)
end

In 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}`)
end

Building 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