Acquis 4 - Table Destructuring

The from keyword enables table destructuring, extracting fields into variables based on their names. This provides a concise alternative to manually accessing each field.

Syntax

The from keyword follows a variable list and precedes a table expression:

local a, b, c from table_expr

This is equivalent to:

local a, b, c = table_expr.a, table_expr.b, table_expr.c

The variable names determine which fields are extracted. The table expression is evaluated once.

Local destructuring

Declare local variables and extract matching fields:

local t = { x = 10, y = 20, z = 30 }
local x, y, z from t
assert(x == 10 and y == 20 and z == 30)

Variables can be listed in any order:

local t = { a = 1, b = 2, c = 3 }
local c, a from t  -- order doesn't match table
assert(a == 1 and c == 3)

Global destructuring

Use global for global variable declarations:

local t = { config = "default", timeout = 30 }
global config, timeout from t
assert(_G.config == "default")
assert(_G.timeout == 30)

Bare assignment

Existing variables can be assigned using bare destructuring:

local x, y
local t = { x = 100, y = 200 }
x, y from t
assert(x == 100 and y == 200)

This is useful for reassignment:

local x, y = 0, 0
local t1 = { x = 1, y = 1 }
local t2 = { x = 2, y = 2 }

x, y from t1
assert(x == 1 and y == 1)

x, y from t2
assert(x == 2 and y == 2)

Missing fields

Fields that don’t exist in the table become nil:

local t = { a = 1 }
local a, b, c from t
assert(a == 1)
assert(b == nil and c == nil)

Nested tables

Destructuring works with nested table access:

local outer = { inner = { value = 42 } }
local value from outer.inner
assert(value == 42)

Function return values

Destructure directly from function calls:

local function getConfig()
    return { host = "localhost", port = 8080 }
end

local host, port from getConfig()
assert(host == "localhost" and port == 8080)

The function is called once, even when extracting multiple fields:

local callCount = 0
local function counted()
    callCount = callCount + 1
    return { a = callCount, b = callCount }
end

local a, b from counted()
assert(callCount == 1)  -- called once
assert(a == 1 and b == 1)

Conditional expressions

Destructure from conditional table expressions:

local useDefaults = false
local defaults = { size = 10 }
local custom = { size = 50 }

local size from (useDefaults and defaults or custom)
assert(size == 50)

Motivation

Lua does not have table destructuring. Extracting multiple fields requires repetitive access expressions.

Repetitive field access

A common Lua pattern:

-- Lua: manual field extraction
local t = { name = "test", value = 42, enabled = true }
local name = t.name
local value = t.value
local enabled = t.enabled

With from:

-- Lus: destructuring
local t = { name = "test", value = 42, enabled = true }
local name, value, enabled from t

Function parameter patterns

Extracting options from a configuration table:

-- Lua: verbose extraction
local function configure(options)
    local host = options.host
    local port = options.port
    local timeout = options.timeout
    -- use host, port, timeout
end

With from:

-- Lus: concise extraction
local function configure(options)
    local host, port, timeout from options
    -- use host, port, timeout
end

Module imports

Extracting functions from a module:

-- Lua: manual extraction
local module = require("mymodule")
local func1 = module.func1
local func2 = module.func2
local func3 = module.func3

With from:

-- Lus: destructured import
local func1, func2, func3 from require("mymodule")