Silent bug in Lua runtime

Today I found an unintended behavior of Lua tables.
It seems that the length of a table is not being correctly computed in the following case:

local arr = table.create(20, 1)
print("Start length: " .. #arr)
for i = 1, #arr do
    if i == 10 or i == 20 then
        arr[i] = nil
    end
end
print("End length: " .. #arr)

This prints:

Start length: 20
End length: 9

The length of the table has changed in a way that does not make any sense.
The following code works properly though:

local arr = table.create(20, 1)
print("Start length: " .. #arr)
for i = 1, #arr do
    if i == 10 then
        arr[i] = nil
    end
end
print("End length: " .. #arr)

and it prints
Start length: 20
End length: 20

The following also works properly:

local arr = table.create(20, 1)
print("Start length: " .. #arr)
for i = 1, #arr do
    if i == 10 or i == 20 then
        arr[i] = false
    end
end
print("End length: " .. #arr)

and prints
Start length: 20
End length: 20

This bug has caused a few of my scripts to malfunction silently. It happens consistently in all scripts in all of my places.

This happens specifically when the last item in a table is an explicit nil and there’s also a hole somewhere.

Examples:

#{1, 2, 3, nil, 5} -- 5
#{1, 2, 3, nil, 5, nil} -- 3

I’m not sure of its status as a bug, because vanilla Lua shares this on all versions too I believe.

2 Likes

I should also point out that the result depends on where the nil’s are and the length of the array:

print(#{1, 2, 3, nil, 5, 6, 7, 8, 9, nil})   -- 9
print(#{1, 2, 3, nil, 5, 6, 7, 8, nil})      -- 3
print(#{1, nil, 3, 4, 5, 6, 7, 8, nil})      -- 8
2 Likes

This is valid according to the Lua Reference:

The length of a table t is defined to be any integer index n such that t[n] is not nil and t[n+1] is nil; moreover, if t[1] is nil, n can be zero. For a regular array, with non-nil values from 1 to a given n, its length is exactly that n, the index of its last value. If the array has “holes” (that is, nil values between other non-nil values), then #t can be any of the indices that directly precedes a nil value (that is, it may consider any such nil value as the end of the array).

2 Likes

After reading that excerpt from the Lua manual, it seems that the # operator has essentially undefined behavior for tables with holes. Considering that it is impossible to predict the result of #t in a table with holes, I would highly suggest that Roblox change this behavior in their new Luau implementation. This behavior is confusing, inconsistent with all other programming languages, and not useful.

As this was intentionally copied over from the old Lua VM, this is not a bug.

Please make a feature request for this in #platform-feedback:engine-features.

1 Like