Determining the Length of a Lua Set

Lua includes a length operator # which (in theory) returns the number of items contained in the value to which it is applied. However, it is frankly a bit wonky and can give unexpected results if used outside of a fairly narrow range of applications. Let's take a look:

Strings

When applied to a string the length operator returns the length of the string:

local x = "abcd"
local y = "ab cd"

print(#x) -- 4
print(#y) -- 5

So far so good, we got some expected results. As shown in the second example, note that spaces (" ") are considered characters, and count in the length just like any other character.

Now, let's take a look at a few more strings:

local x = "中文"
local y = "zhōng wén"
local z = "😊"

-- careful!
print(#x) -- 6
print(#y) -- 11
print(#z) -- 4

These are (probably) not what we expected. What happened? It turns out that the length operator doesn't return the number of characters in the string, it returns the number of bytes of memory occupied by the string.

When strings exclusively contain ASCII characters, which each occupy 1 byte of memory, the length operator returns the result that one would generally expect. However, when a string contains non-ASCII characters such as unicode strings, which can occupy multiple bytes, this operator can return unexpected results.

Tables

We learned earlier than tables are collections of values. It can often be helpful to know how many values are contained in a collection. Let's look a list-like tables first:

Lists

Let's create a list then see how many items are in it:

local x = { 1, 2, 3, 5, 6 }

print(#x) -- 5

Ok, that worked as we might expect. Five values we added to the list, and the length operator returned a length of 5. Let's see what happens with a list of nil values:

local y = { nil, nil }

print(#y) -- 0

Hmm, the list contains 2 nil values, but returns a length of 0. Should we have expected a length of 0 or a length of 2? On one hand there are 2 nil values in the list, but on the other hand nil is considered to be "not a value".

Let's leave that debate to the philosophers, the important thing for programmers is that Lua has an opinion about this, and applies that opinion consistently. So, based on this result we can conclude that Lua does not count nil values in lists.

Let's look at another example:

local x = { 1, 2, 3, nil, 4, 5 }

print(#x) -- 6

Er, hold on. Lua counted nil as a value this time?

It turns out that the process Lua uses to calculate the length of list-like tables is not as simple as counting values in the list, and gets complicated when the list contains nil values. Even worse, the result that is returned depends not only on the values that are in the list at the time that the length operator is applied, it depends on how the table was created and populated. Let's see what happens when we build this table another way:

local y = {}
y[1] = 1
y[2] = 2
y[3] = 3
y[4] = nil
y[5] = 4
y[6] = 5

print(#y) -- 3

Although these tables contain the same values, this table has a length of 3? After digging through the Lua documentation to gain a better understanding of how this operator works, we have learned that the situation is even worse than expected - this example could have returned any of 2, 3, 5, or 6.

So, while the length operator can be useful with list-like tables, care must be taken to ensure that the lists it operates don't contain any nil values.

Mappings

Now let's look at another application of tables, mappings. The following table is created with 5 key-value pairs. Since it contains 5 values, one might expect this table to have a length of 5. Let's see:

local x = {
a = 1,
b = 2,
c = 3,
d = 4,
e = 5,
}

print(#x) -- 0

Hmm, that was not expected. The table clearly has 5 values in it, but the result is 0.

It turns out the that length operator only considers the number of list-like values contained in a table, and does not consider mappings at all.