Tables

Work in progress

This page is not up to our standards yet.

This can be because is not complete or hasn't been reviewed and copyedited properly. Some details may be poorly worded, wrong, or unclear. Please let me (deviaze) know on Discord if you need clarification on anything.

A table stores values under keys. Any value except nil and NaN can be a key. A table with the keys 1, 2, 3 and so on is an array, and a table with other keys is a map. When those keys are names you know ahead of time, like a shop item's name and price, the table is a record.

Every table has an array portion and a hash portion. Consecutive integer keys starting at 1 are usually stored in the array portion and every other key goes in the hash portion. One table can use both.

# only measures the array portion. Using it on a map or a record is a mistake: it gives 0 for a table whose keys are all in the hash portion.

Input
const gold = { Ada = 120, Grace = 75 }
print(#gold)
Output
0

Arrays

An array is a table of values in order. Write the values between braces separated by commas. Indices start at 1 rather than 0 and # gives the length:

Input
const fruits = { "apple", "kiwi", "pear" }
print(fruits[1])
print(fruits[#fruits])
print(#fruits)
Output
apple
pear
3

Adding and removing items

table.insert adds an item at the end or at an index you give it and table.remove takes one out and returns it. table.concat joins an array of strings or numbers into one string:

Input
const queue = { "Ada", "Grace" }
table.insert(queue, "Alan") -- at the end
table.insert(queue, 1, "Linus") -- at index 1
const first = table.remove(queue, 1)
print(first)
print(table.concat(queue, ", "))
Output
Linus
Ada, Grace, Alan

queue is a const and its contents still changed. const only stops the variable from pointing at a different table. The table itself can always change.

Sorting

table.sort sorts an array in place from smallest to largest. To sort by anything else, pass a function that says whether one item goes before another. Functions shows how.

Input
const scores = { 72, 95, 88 }
table.sort(scores)
print(table.concat(scores, " "))
Output
72 88 95

Maps

A map (or dictionary) stores each value under a key. Almost any value can be a key: strings, numbers, booleans and even other tables. Write the key in square brackets:

Input
const gold: { [string]: number? } = { ["Ada"] = 120, ["Grace"] = 75 }
gold["Alan"] = 30
print(gold["Alan"])
print(gold["Linus"])
Output
30
nil

Reading a key that isn't there gives nil instead of an error. Setting a key to nil removes it from the table.

Records

When the keys are names you know ahead of time, the table is a record. name = "Ada" is shorthand for ["name"] = "Ada" and player.name is shorthand for player["name"]. A record can hold other tables:

Input
const player = {
    name = "Ada",
    level = 12,
    position = { x = 4, y = 7 },
}
player.level += 1
print(`{player.name} is level {player.level}`)
print(`standing at {player.position.x}, {player.position.y}`)
Output
Ada is level 13
standing at 4, 7

Looping over tables

for key, value in t do visits every entry of t. An array's items come in order from index 1 and a map's entries come in no particular order.

Input
const gold: { [string]: number? } = { Ada = 120, Grace = 75, Alan = 30 }
local total = 0
for _, amount in gold do
    total += amount
end
print(total)
Output
225

Lua code loops over arrays with for i, v in ipairs(array) and over maps with for k, v in pairs(map). Both still run in Luwu but are deprecated, and the LuaIterators lint suggests the plain loop. ipairs stops at the first missing item, while pairs visits every entry in any order.

Holes in arrays

nil means a value isn't there. Assigning nil in the middle of an array leaves a hole that # may count up to or past. Keep arrays free of holes and use table.remove to take items out.

When an array needs a slot that's intentionally empty, Luwu's none primitive can be stored in a table where nil can't.

Tables are shared, not copied

Assigning a table to another variable doesn't copy it. Both variables refer to the same table and a change through one shows up through the other. table.clone makes a copy when you need one. The copy is shallow: tables inside it are still shared.

Input
const original = { 1, 2, 3 }
const alias = original
alias[1] = 100
print(original[1])

const copy = table.clone(original)
copy[1] = 1
print(original[1])
Output
100
100

Table types

Luwu checks your code's types before it runs. Each kind of table has a different way to write its type:

-- an array of strings
const fruits: { string } = { "apple", "kiwi" }

-- a map from strings to numbers
const gold: { [string]: number? } = { Ada = 120 }

-- a record with named fields
type Player = { name: string, level: number }
const ada: Player = { name = "Ada", level = 12 }

print(fruits[1], gold.Ada, ada.name)

The ? in number? means the value can also be nil. A map lookup can find nothing and the type says so. Type Checking covers types in more depth.

Going further

Table Comprehensions build a new table from a loop in one expression, and Destructuring pulls several fields out of a table at once.