More Lua Gotchas

Posted in lua, programming

Speaking of Lua gotchas, here’s another one. In Lua, a function with n parameters is similar to a variadic function. As an example, consider:

function f(a, b)
    print(a, b)
end

We can call f with as many arguments as we like. Missing arguments become nil, extra arguments are discarded:

f()        -- nil nil
f(1)       -- 1   nil
f(1, 2)    -- 1   2
f(1, 2, 3) -- 1   2

In fact, function f could be written as:

function f(...)
    local a, b = ...
    print(a, b)
end

The reference manual has the following to say about local a, b = ...:

Before the assignment, the list of values is adjusted to the length of the list of variables. If there are more values than needed, the excess values are thrown away. If there are fewer values than needed, the list is extended with as many nil’s as needed. If the list of expressions ends with a function call, then all values returned by that call enter the list of values, before the adjustment (except when the call is enclosed in parentheses; see §3.4).

There is no observable difference, whether we call f(1) or f(1, nil). But for some functions, there is. Suppose we have

table.insert(t, g())

where t is a table and g is a function that may or may not return a value. As long as g returns a value, including nil, everything works as expected. (We don’t mind appending nil to a table.) When g happens to return nothing, table.insert complains about a missing argument, which may be surprising, considering that Lua is supposed to adjust the number of arguments to the number of parameters:

stdin:1: wrong number of arguments to 'insert'
stack traceback:
    [C]: in function 'insert'
    stdin:1: in main chunk
    [C]: in ?

We can introduce a variable

local b = g()
table.insert(t, b)

or put parentheses around the call to g

table.insert(t, (g()))

to force exactly one result, turning nothing into nil, as if g returned nil. And nil is not nothing. All three functions

function f() end
function g() return end
function h() return nil end

are interchangeable when used liked this:

local x = f(); print(x) -- nil
local y = g(); print(y) -- nil
local z = h(); print(z) -- nil

But only h returns nil, whereas f and g return nothing:

print(f()) --
print(g()) --
print(h()) -- nil