1. Performance and memory layout
-
Array part vs hash part:
Lua tables have an array part (for integer keys 1..n) and a hash part (for everything else, like "X", "Y", "Z").
Numerical indexing lets Lua store your components in the array part, which is:
- Faster to access (no hashing, no string lookup)
- More compact in memory
-
Tight loops:
Math-heavy code (physics, rendering, linear algebra) often runs in tight loops.
Doing:
is cheaper than:
especially when repeated thousands of times per frame.
2. Generic algorithms become trivial
Numerical indexing makes math code dimension-agnostic:
local function add(a, b, out)
out = out or {}
for i = 1, #a do
out[i] = a[i] + b[i]
end
return out
end
That works for:
Vector2D (size 2)
Vector (size 3)
Vector4D (size 4)
- Arbitrary-length vectors
Whereas if you use named fields (X, Y, Z), you either:
- Write separate functions for each dimension, or
- Hardcode field names, which doesn’t scale.
Same for matrices:
for i = 1, rows do
for j = 1, cols do
c[i][j] = a[i][j] + b[i][j]
end
end
That’s only natural with numeric indices.
3. Interop with array-based APIs
When you need to...
- Serialize to binary formats
- Talk to C/C++ via FFI
- Pass data to shaders or engines that expect flat arrays
…then having your vectors/matrices as numeric arrays is a direct fit:
-- Vector as {X, Y, Z}
buffer[1] = v[1]
buffer[2] = v[2]
buffer[3] = v[3]
No mapping from X, Y, Z to indices every time. It's nicer to Lua binary module authors...
4. Cleaner metatable tricks
You can have the internal representation be numeric, but still expose X, Y, Z for ergonomics:
local Vector = {}
local Vector_mt = {}
function Vector.new(x, y, z)
return setmetatable({ x, y, z }, Vector_mt)
end
setmetatable(Vector, {
__call = function(cls, ...)
return cls.new(...)
end
})
function Vector_mt.__index(t, k)
-- This or use lookup/dispatch table
if k == "X" then return rawget(t, 1)
elseif k == "Y" then return rawget(t, 2)
elseif k == "Z" then return rawget(t, 3)
else return rawget(Vector_mt, k) end
end
function Vector_mt.__newindex(t, k, v)
-- This or use lookup/dispatch table
if k == "X" then rawset(t, 1, v)
elseif k == "Y" then rawset(t, 2, v)
elseif k == "Z" then rawset(t, 3, v)
else rawset(t, k, v) end
end
return Vector
This gives:
- Fast numeric storage (
t[1], t[2], t[3])
- Nice field access (
t.X, t.Y, t.Z)
- Shorthand constructor (
Vector(1, 2, 3) instead of Vector.new(1, 2, 3))
Best of both worlds. 😎
5. Consistency with math libraries and conventions
Most math libraries (in Lua and other languages) treat vectors/matrices as arrays:
v[1], v[2], v[3]
m[row][col]
Using numeric indexing:
- Makes code feel familiar to anyone with linear algebra background
- Plays nicely with generic utilities (
map, reduce, #v, etc.)
6. Easier dimension changes and refactors
If you start with Vector and later need Vector4D (e.g., homogeneous coordinates), numeric indexing makes it a non-event:
- Algorithms that loop
1..#v just keep working.
- No need to introduce a new field name (
W) everywhere.
TL;DR
Using numeric indexing for Vector/matrices:
- Leverages Lua’s array optimization (speed + memory)
- Enables generic math code that works for any dimension
- Fits better with FFI/binary/shader-style APIs
- Still allows ergonomic
v.X/v.Y/v.Z via metatables if you want
1. Performance and memory layout
Array part vs hash part:
Lua tables have an array part (for integer keys
1..n) and a hash part (for everything else, like"X","Y","Z").Numerical indexing lets Lua store your components in the array part, which is:
Tight loops:
Math-heavy code (physics, rendering, linear algebra) often runs in tight loops.
Doing:
is cheaper than:
especially when repeated thousands of times per frame.
2. Generic algorithms become trivial
Numerical indexing makes math code dimension-agnostic:
That works for:
Vector2D(size 2)Vector(size 3)Vector4D(size 4)Whereas if you use named fields (
X, Y, Z), you either:Same for matrices:
That’s only natural with numeric indices.
3. Interop with array-based APIs
When you need to...
…then having your vectors/matrices as numeric arrays is a direct fit:
No mapping from
X, Y, Zto indices every time. It's nicer to Lua binary module authors...4. Cleaner metatable tricks
You can have the internal representation be numeric, but still expose
X, Y, Zfor ergonomics:This gives:
t[1], t[2], t[3])t.X, t.Y, t.Z)Vector(1, 2, 3)instead ofVector.new(1, 2, 3))Best of both worlds. 😎
5. Consistency with math libraries and conventions
Most math libraries (in Lua and other languages) treat vectors/matrices as arrays:
v[1], v[2], v[3]m[row][col]Using numeric indexing:
map,reduce,#v, etc.)6. Easier dimension changes and refactors
If you start with
Vectorand later needVector4D(e.g., homogeneous coordinates), numeric indexing makes it a non-event:1..#vjust keep working.W) everywhere.TL;DR
Using numeric indexing for
Vector/matrices:v.X/v.Y/v.Zvia metatables if you want