Varun Ramesh's Blog

Lua Gotchas

Friday, October 20th 2017

Lua is an awesome language, but it has some unusual design choices that can cause endless frustration to beginners (and experts). This post goes over these gotchas1.

Table of Contents

Variables are Global Unless Specified with local

In Lua, variables are globally scoped by default. This has lead to many problems for me, since a temporary variable used by a function can be overwritten by anything called by that function. Here is a somewhat contrived example, where two functions both use the variable name temp as an intermediate value, thereby overwriting each other. Running this snippet and entering 3, and 4 produces the result 8, when it should return 7. Try this snippet on repl.it!

function readNumber()
  print("Type a number:")
  temp = io.read("*number")
  return readAnotherNumber() + temp
end

function readAnotherNumber()
  print("Type another number:")
  temp = io.read("*number")
  return temp
end

We can fix this by adding the local modifier to the temp assignment, as follows:

function readNumber()
  print("Type a number:")
  local temp = io.read("*number")
  return readAnotherNumber() + temp
end

function readAnotherNumber()
  print("Type another number:")
  local temp = io.read("*number")
  return temp
end

Here’s something from one of my projects - its a backtracing search. Can you spot the bug?

function best_next_action(actions_so_far)
  if #actions_so_far == SEARCH_DEPTH then
    return nil, eval_actions(actions_so_far)
  end

  best_action, best_score = nil, -math.huge
  for next_action=-1, 1, 2/(STEERING_BINS - 1) do
    table.insert(actions_so_far, next_action)
    _, score = best_next_action(actions_so_far)
    if score > best_score then
      best_score = score
      best_action = next_action
    end
    table.remove(actions_so_far)
  end

  return best_action, best_score
end

I forgot to make best_action and best_score local, thereby making recursive calls destroy the caller’s value. This bug took hours to find. score should also be made local, though in this situation it’s not a problem.

I recommend you do yourself a favor and get in the habit of making every variable a local by default.

Functions are Global Unless Specified with local

Similar to variables, function in Lua are global by default, unless you specify the local keyword. This can cause an issue if you have an inline function that has the same name as a global function, or if you try to return functions in a closure. Here’s an example where this is an issue.

function make_counter(i)
  function incr() i = i + 1 end

  return function()
    incr(); return i
  end
end

local a, b = make_counter(0), make_counter(0)

print(a(), a(), a())
print(b(), b(), b())

This is supposed to print out 1 2 3 twice, but instead prints out 0 0 0 and 4 5 6, because the incr function ends up being shared between the two instances of the counter. Try the code out on repl.it.

The code can be fixed by simply adding the local keyword to the incr function.

function make_counter(i)
  local function incr() i = i + 1 end

  return function()
    incr(); return i
  end
end

a.func(...) vs a:func(...)

Tables in lua are just dictionaries from keys to values. The . operator can be used to get the value for certain keys. To simulate object-oriented programming you could have a table represent an object, with fields as entries. Methods would also be entries, but the value would be a function that takes in the object itself as the first argument.

local point = {
  x = 0, y = 0,
  print = function(self) print(self.x, self.y) end
}
point.print(point)

This syntax can feel redundant. Because it’s such a common pattern, Lua introduces a special : operator. You should think of a:func(...) as equivalent to a.func(a, ...).

However, this can lead to mixups if you accidentally use the wrong operator. If you wrote . but were supposed to write :, then you might get the error message attempt to index local 'self'. Otherwise, you might get some confounding type error further down the road. This is further complicated by the fact that some libraries want you to use . for objects, and thus store the variable self in a closure.

Tables are Both Lists and Dictionaries

In many languages, dictionaries and lists are separate datatypes, but in Lua these concepts are merged into one datastructure - the table. The Table is effectively a dictionary where keys and values can be anything except nil (assigning a value to be nil is the same as deleting that key, while nil simply can’t be a key). Thus one data structure can act as both a hashmap and a list, and even be both at the same time.

The Behavior of ipairs vs. pairs

pairs iterates over every key in a table, whether it’s an array-like key or a map-like key. ipairs on the other hand, has the unusual behavior that it starts at the key of 1 and then keeps incrementing the key until the value is nil. Take a look at the following cases below that shows the unusual results this behavior can create. You can run the code on repl.it.

function ipairs_print(t)
  for _, v in ipairs(t) do print(v) end
end

ipairs_print({1, 2, 3, nil, 4}) -- Prints 1, 2, 3
ipairs_print({nil, 1, 2, 3, nil, 4}) -- Prints nothing
ipairs_print({[0]=1, [1]=2, [2]=3}) -- Only prints 2, 3

The Behavior of #

The # operation is confusing in that it can actually return different results for the same table, depending on how the table was constructed. From the manual:

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.

You may notice that, if your array has holes, there can be multiple such indices n, therefore the # operator can return any of those indices as a valid answer. Below is an example that showcases this unusual behavior, which you can run on repl.it.

print(#{[1]='a', [2]='a', [4]='b'}) -- Prints 4
print(#{[1]='a', [2]='a', [5]='b'}) -- Prints 2
print(#{'a', 'a', nil, nil, 'b'}) -- Prints 5

The bottom line is, unless you know for sure that your array doesn’t have holes, don’t use #.


  1. More gotchas can be found at this link - http://www.luafaq.org/gotchas.html 

#lua #programming-languages

Similar Posts

How I Structure GameObjects - Components and...
Simple Tips to Level Up Your Python Programming Skills
Stackless vs. Stackful Coroutines