Tables
Tables are Saule’s only data structure — same model as Lua. A single table holds both an array part (contiguous 1-based integer keys) and a map part (everything else: strings, booleans, non-positive integers). The two parts share one value space and one length.
Table Types
Section titled “Table Types”A table type is written in one of two forms:
| Form | Meaning |
|---|---|
table<V> |
Array: integer keys, V values |
table<K, V> |
Map: K keys (integer or string), V values |
table |
Any table — element types unknown |
table<V> and table<integer, V> are the same type; the array form just
leaves the implicit integer key unwritten.
local names: table<string> = {"alice", "bob"} -- array of stringlocal same: table<integer, string> = names -- identical typelocal ages: table<string, integer> = {alice: 30} -- string-keyed maplocal nested: table<table<string>> = {names} -- tables nestArray Suffixes
Section titled “Array Suffixes”T[] is sugar for table<integer, T> — the integer-keyed array form, written
the way most languages write an array:
local names: string[] = {"alice", "bob"}local same: table<integer, string> = names -- the same type, spelled outlocal also: table<string> = names -- and the same as the array formIt is only notation. There is no separate array type: string[],
table<integer, string> and table<string> are one type with three
spellings, so they are mutually assignable, and all three print as
table<integer, string> when a message reports a type back.
Each suffix wraps the type once, so they stack:
| Written | Means |
|---|---|
T[] |
table<integer, T> |
T[][] |
table<integer, table<integer, T>> |
T[,] |
table<integer, table<integer, T>> — a comma is another dimension |
T[,,] |
three levels, the same as T[][][] |
T[,] and T[][] are the same type. Saule’s tables nest rather than being
rectangular, so there is nothing for a comma to mean other than another
level, and no dimension is checked: a T[,] can hold rows of different
lengths.
local grid: integer[][] = {{1, 2}, {3, 4}}local same: integer[,] = grid -- identical typeprintln(grid[2][1]) -- 3Suffixes apply left to right, which is what makes ? read correctly beside
one:
class Dog name: string = "rex"end
local maybeList: Dog[]? = nil -- a nullable arraylocal maybeDogs: Dog?[] = {Dog(), nil} -- an array of nullable Dog
println(maybeList == nil) -- trueprintln(#maybeDogs) -- 2There is no [K] form for map keys — a string-keyed table stays
table<string, V>. A suffix only ever adds an integer-keyed level, because
that is the one case with an implicit key to leave out.
Element Types Are Invariant
Section titled “Element Types Are Invariant”Tables are mutable, so a table<Dog> is not a table<Animal> — in
either direction. Writing through the wider name would put an Animal into
a table the narrower name still believes holds only Dogs:
class Animal fn speak() -> string return "..." endend
class Dog extends Animal fn fetch() -> string return "ball" endend
local dogs: table<Dog> = {}local animals: table<Animal> = dogs -- ERROR: table<Dog> is not table<Animal>Table.insert(animals, Animal()) -- ...this is whyThe same applies to key types: table<string, integer> and
table<integer, integer> are unrelated.
An empty {} literal has no element type yet, so it fills any table slot,
and a bare table annotation accepts anything.
Literals
Section titled “Literals”-- Array part (positional entries — auto-indexed from 1).local nums: table<integer> = {10, 20, 30}print(nums[1]) -- 10print(#nums) -- 3
-- Map part (named entries).local p: table = { name: "Arthur", health: 100, alive: true }
-- Mixed: positional first, then named.local mix: table = { "a", "b", color: "red", 99 }Keys in { key: value } literals can be bare identifiers or quoted strings — both produce a string-keyed map entry.
Computed keys
Section titled “Computed keys”A key in brackets is an expression, evaluated where it is written:
local field: string = "name"local row: table<string, string> = { [field]: "Ada", role: "author" }
-- Any key type the table accepts, not only strings:local byId: table<integer, string> = { [1 + 1]: "two", [3]: "three" }The brackets are what distinguishes the two meanings: a bare name: is sugar
for the string key "name", so a key that should be computed has to say so —
{ field: "Ada" } keys by the text "field", { [field]: "Ada" } by what
field holds. ["name"] and name: are the same key written two ways, and a
computed key is checked against the table’s declared key type exactly as
t[k] = v would be.
Indexed Access
Section titled “Indexed Access”t[k] accepts any value as a key:
local scores: table = {}scores["arthur"] = 50scores["merlin"] = 80scores[1] = "first place"Lua-style Dotted Access
Section titled “Lua-style Dotted Access”t.foo is equivalent to t["foo"] — both read and write, in any combination:
local cfg: table = {}cfg.title = "My Game" -- same as cfg["title"] = "My Game"cfg["width"] = 1920 -- same as cfg.width = 1920
print(cfg.title) -- "My Game"print(cfg["width"]) -- 1920print(cfg.missing) -- nil (no error — missing keys yield nil)This is plain map sugar, so it only applies to tables. Class instances and statics keep their strict obj.field semantics: writing a previously-undeclared field on an instance is still a compile error.
Length
Section titled “Length”#t returns the array length — the count of contiguous integer keys starting at 1. Map entries don’t contribute to #:
local t: table = {10, 20, 30, name: "tags"}print(#t) -- 3Removing Keys
Section titled “Removing Keys”Assigning nil clears a map entry’s value but does not remove its
key — so JSON-style {"x": null} round-trips faithfully:
local user: table<string, any> = {}user["name"] = "Arthur"user["draft"] = true
user["draft"] = nilprint(user["draft"] ?? "no value") -- no valueprint(#Table.keys(user)) -- 2: the key is still thereThere is no function that drops a single map key. Table.remove(t, i) takes
an integer position in the array part and shifts the elements after it
down, which is a different operation; Table.clear(t) empties the whole
table. To build a map without a key, leave it out rather than setting it and
removing it.
Iterating
Section titled “Iterating”for v in t walks the array part. for k, v in t walks both the array and map parts (key/value iteration). See Loops and the Standard Library for the full set of helpers (Table.insert, Table.sort, Table.concat, …).