As I understand Lua, an array is just a table where the keys start at 1 and increment sequentially. However, there are features within Lua to manipulate arrays in the traditional sense (e.g. ipairs, square-bracket syntax, etc.). So Lua does "support" the concept of arrays.
In Lua, you could initialize an array like this:
-- Code Example "A"
a={}
a[1] = "abc"
a[2] = "xyz"
a[3] = "foo"
That works, but is sometimes abbreviated as follows:
-- Code Example "B"
a={ "abc", "xyz", "foo" }
This also works and is equivalent to example "A". Furthermore, Lua also provides a way to initialize an an array by specifying each index, as follows:
-- Code Example "C"
a={
[1] = "abc",
[2] = "xyz",
[3] = "foo"
}
The above syntax should have identical semantics to examples "A" and "B". However, example "C" suffers from an insidious problem, which examples "A" and "B" do not suffer from. In particular, given the variable "a" from example "C" , you can't use the normal Lua design-pattern to determine that "a" can be treated as an array (as opposed to a table). The well-known Lua pattern for determining if a table is an array is as follows:
table_is_array = (type(a)== "table") and (#a>0) and (next(a,#a)==nil)
In particular, the expression next(a,#a)
returns a value of nil for code examples "A" and "B" (which is correct to indicate an array), while the expression next(a,#a)
returns a value of 1 for code example "C" (which is incorrect for an array). The question is, why are semantically identical array initialization syntaxes producing different results with the Lua "next" function? Clearly all the code examples "A", "B", and "C" are all doing exactly the same thing, but yet they produce different results with the Lua "next" function. Worse, the differing results interfere with the most common way to determine if a table can be treated as an array in Lua.
So to recap:
a={}
a[1] = "abc"
a[2] = "xyz"
a[3] = "foo"
t={
[1] = "abc",
[2] = "xyz",
[3] = "foo"
}
print("next(a,#a) = " .. tostring(next(a,#a))) -- displays nil (correct)
print("next(t,#t) = " .. tostring(next(t,#t))) -- displays 1 (incorrect)
I need to initialize the table as in example C because in my code, the index values are in variables, similar to this example:
i_abc=1
i_xyz=2
i_foo=3
v_abc="abc"
v_xyz="xyz"
v_foo="foo"
-- Now put the above data into an array
a={
[i_abc] = v_abc,
[i_xyz] = v_xyz,
[i_foo] = v_foo
}
Why does the array-checking pattern fail for example C?
From Lua 5.4, §3.4.9 – Table Constructors, the syntax (in EBNF) is described as:
tableconstructor ::= ‘{’ [fieldlist] ‘}’
fieldlist ::= field {fieldsep field} [fieldsep]
field ::= ‘[’ exp ‘]’ ‘=’ exp | Name ‘=’ exp | exp
fieldsep ::= ‘,’ | ‘;’
The important bits of text from that section are (emphasis mine):
Each field of the form [exp1] = exp2 adds to the new table an entry with key exp1 and value exp2. A field of the form name = exp is equivalent to ["name"] = exp. Fields of the form exp are equivalent to [i] = exp, where i are consecutive integers starting with 1; fields in the other formats do not affect this counting.
[ ... ]
The order of the assignments in a constructor is undefined.
So only the plain syntax of { 'a', 'b', 'c' }
influences the internal count of the array-like portion of the table. In the explicit syntax of { [1] = 'a', [2] = 'b', [3] = 'c' }
, the keys, despite being densely incrementing integers in this case, are treated purely as associative, and the order in which they are added to the table is not defined by the specification.
The order of operations in both your A and B examples are understood to be fixed, and clearly align with your implementation of Lua and your (mis)use of next
. These results are misleading.
The real problem comes from the use of next (table [, index])
:
The order in which the indices are enumerated is not specified, even for numeric indices. (To traverse a table in numerical order, use a numerical for.)
The "well-known Lua pattern" you have described for determining if a table is an array is problematic (falls apart based on implementation, and the presence of multiple borders). I would quickly argue that, by default, all Lua tables are arrays (although they may not be a sequence), and you can generally just treat them as such by iterating from index 1 until the first nil-value index (i.e., ipairs
).
If you really need to be exact (reject sparse arrays or mixed key types), you can:
Check for an empty table with not next(t)
;
or check that an index of 1 contains a value with rawget(t, 1) ~= nil
;
then, iterate through all key-value pairs (i.e., pairs
) and confirm that all key types are numeric integers, and that the number of keys matches the border found by the length operator (#
).
In summary, the behaviour of the table constructor syntax is not a bug in Lua.
Quick aside: it is hard not to level some criticism at your example, because it is difficult to understand how you would arrive at such a use-case. If you are trying to move data between languages, use an interchange format, like the previously mentioned JSON - there are pure Lua and C API implementations available.
If this is machine generated code, change the generation code.