Hello Roblox developers,
I recently stumbled upon an interesting aspect of Luau pertaining to how the # operator behaves when used on tables with non-sequential integer keys, and I’m hoping for some clarification from anyone who might have more insights into this.
Specifically, I’m trying to understand the logic behind the lengths returned by the # operator for tables where there’s a “hole” (a nil value) in the integer keys.
As an example, let’s consider two tables:
local tbl1 = {
[1] = true,
[2] = true,
[4] = true,
}
print(#tbl1) -- Prints 4
and
local tbl2 = {
[1] = true,
[3] = true,
}
print(#tbl2) -- Prints 1
In both tables, the index before the last is nil. However, #tbl1 returns 4 while #tbl2 returns 1. I understand from Lua’s language reference manual that “the length of a table is any integer index n such that t[n] is not nil and t[n+1] is nil.” But this logic seems to be pretty inconsistent and unpredictable.
An additional case:
local tbl3 = {
[1] = true,
[2] = true,
[3] = true,
[5] = true,
[6] = true,
}
print(#tbl3) -- Prints 6
Additionally, I’ve noticed that the behavior of the # operator seems to change depending on whether a nil value is explicitly assigned to an index or if the index is simply omitted. Consider these examples:
Explicitly assigning nil:
local tbl = {
[1] = true,
[2] = true,
[3] = true,
[4] = nil,
[5] = true,
}
print(#tbl) --prints 5
Omitting the index:
local tbl = {
[1] = true,
[2] = true,
[3] = true,
[5] = true,
}
print(#tbl) --prints 3
The lengths returned in these two cases are different, despite the fact that tbl[4] is functionally nil in both cases.
Further testing revealed a sequence of table lengths where Luau allows for the index before the last to not exist and still return the table’s “true” length, including the missing index:
1, 4, 7, 8, 11, 13, 15, 16, 19, 21, 23, 25, 27, 29, 31, 32, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 64, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99
Most numbers in the sequence increase by 2 or 3, except for jumps from 1 to 4, 7 to 8, 31 to 32, and 63 to 64.
If anyone has insight into how Luau determines the array “boundary” of a table and can shed some light on it that would be greatly appreciated. I understand that this behavior is implementation-dependent, but I’ve been going crazy trying to find some sort of explanation or reason behind this.
Thanks!