The table length "#" operator returns the index of the largest integer key continuously reachable from the index at 1. (i.e. there can be no integer keys with nil values in between). This, as well as a handful of standard library operators, rely on the index-from-1 convention.
That does not always hold true. The '#' operator is weird. Here is an example:
/*
** Try to find a boundary in table `t'. A `boundary' is an integer index
** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
*/
int luaH_getn (Table *t) {
unsigned int j = t->sizearray;
if (j > 0 && ttisnil(&t->array[j - 1])) {
/* there is a boundary in the array part: (binary) search for it */
unsigned int i = 0;
while (j - i > 1) {
unsigned int m = (i+j)/2;
if (ttisnil(&t->array[m - 1])) j = m;
else i = m;
}
return i;
}
/* else must find a boundary in hash part */
else if (t->node == dummynode) /* hash part is empty? */
return j; /* that is easy... */
else return unbound_search(t, j);
}
I did not get the same behavior as you when I tried this just now - rather, I had:
> x.a = 10
> print(#x)
0
>
Which makes more sense than 1. At the same time, 3 is also a valid value of #x, because it is the integer index of a value directly preceding an index with a nil value.
This behavior has been hashed over on the mailing list multiple times... the exact wording of the documentation allows for it but it's not exactly what people expect of the length operator.
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). -- From the manual
My expectation is that there will be some changes to the handling of the # operator in 5.2 increasing its usefulness and allowing it to be overridden via metatables for user-defined types.
In any case, this is one of the few currently ugly corners of the language itself. Another would be the handling of destructuring varargs via the select(...) function, which can be really funky.
This is one of the things expected to be fixed in 5.1.4, IIRC.
# works with an array-style table (consecutive integer keys, starting from 1) and not hash-style tables. Once you're familiar with Lua, you rarely confuse them -- they tend to be used very differently. Using the same variable type is mostly an efficiency trick.
I agree that counting from 1 is kind of annoying, but part of their reasoning was that Lua is targeted as a scripting language for niches that can include people who don't really program. They tried to keep "weird" programming stuff from obfuscating the tiny amount necessary for writing config or data files in Lua. It has coroutines, lexical scope, metatables, etc. when you use it for real programming, but they don't get in the way when you don't need them.
For example, from the data file for my laptop's wifi script:
That does not always hold true. The '#' operator is weird. Here is an example:
ipairs() stops at the first nil, as expected: However #x is inaccurate now: Luckily, we can always see all the values with pairs(): And watch what happens when we add a single non-integer key to the array: Looking at http://www.lua.org/source/5.1/ltable.c.html#luaH_getn I think the problem lies in the assumption in luaH_getn that if the table doesn't have an 'hash' part then it also doesn't have any gaps.