Lua 5.1 / LuaJIT Quick Summary (part 1: types, control flow, and scope)

Read about the Lua/LuaJIT language here, including documentation and resource links.

Go to part 2.

Syntax

-- Two dashes create a comment that ends at the line break
print("hello world")
-- Note: there is no need for semicolons to terminate statements.

Simple types

All values in Lua are one of the following types; however unlike C, the type does not belong to the variable, but to the value itself. That means that a variable can change type (dynamic typing).

-- Unlike C, the type information is stored with the value, not the variable; 
-- this means that the type of a variable can change dynamically.
x = 1           -- x now refers to a number
x = "foo"       -- x now refers to a string

-- check types like this:
if type(x) == "number" then 
    -- do stuff
end

Numbers

All numbers in Lua are 64-bit floating point type (aka ‘double’ for C programmers). There is no distinction between integers and non-integers.

-- these lines are all equivalent
-- they assign the number value 1 to the variable name x:
x = 1
x = 1.0
x = 100e-2  -- e base10 format
x = 0x1 --hexadecimal format
-- Note: all numbers in Lua are 64-bit doubles

The exception is FFI objects, which present any C type to Lua, including number types.

Strings

Lua strings are immutable: each string operation creates a new string.

-- strings:
print("a simple string")
print('a simple string')

-- embedding special characters and multi-line strings:
x = 'escape the \' character, and write \n a new line'
x = [[
The double square brackets are a simple way to write strings
that span
over several
lines]]

Strings are hashed internally very efficiently, and garbage collected.

Booleans and nil

Boolean values are the keywords true and false. The nil value indicates the absence of a value, and also counts as false for conditional tests.

-- Boolean values:
t = true
f = false
if t then print("t!") end -- prints t!
if f then print("f!") end -- prints nothing

-- nil indicates the absence of value. 
-- Assigning nil to a variable marks the variable for garbage collection.
n = nil
-- nil also evaluates to false for a predicate:
if n then print("n!") end -- prints nothing

Assigning nil to a variable removes a reference to the value; if the value is no longer accessibly referenced by code, it can be garbage collected. Assigning nil to a table key effectively removes that key from the table.

Tables (structured data)

Lua provides only one data structure: the table. Tables in Lua are associative arrays, mapping keys to values. Both keys and values can be any valid Lua type except nil. However, the implementation makes sure that when used with continuous number keys, the table performs as a fast array.

-- creating an array-like table of strings, the quick way:
t = { "one", "two", "three" }

-- creating a dictionary-like table, the quick way:
t = { one = 1, two = 2, three = 3 }

-- creating a table with both array-like and dictionary-like parts:
t = { "one", "two", "three", one = 1, two = 2, three = 3 }

-- create an empty table:
t = {}

-- add or replace key-value pairs in the table:
t[1] = "one"    -- array-like
t["two"] = 2    -- dictionary-like
-- a simpler way of saying that:
t.two = 2

print(t.two, t["two"])    --> 2 2

-- special case of nil:
-- remove a key-value pair by assigning the value nil:
t.two = nil
print(t.two)          --> 

-- create a table with a sub-table:
t = {
    numbers = { 1, 2, 3 },
    letters = { "a", "b", "c" },
}

-- any Lua type (except nil) can be used as key or value
-- (including functions, other tables, the table itself, ...)
t[x] = t
t[function() end] = false
t[t] = print
-- and other madness...

It’s important to remember that a Lua table has two parts; an array-portion and a hash-table portion. The array portion is indexed with integer keys, starting from 1 upwards. All other keys are stored in the hash (or record) portion.

The array portion gives Lua tables the capability to act as ordered lists, and can grow/shrink as needed (similar to C++ vectors). Sometimes the array portion is called the list portion, because it is useful for creating lists similarly to LISP. In particular, the table constructor will insert numeric keys in order for any values that are not explicitly keyed:

-- these two lines are equivalent
local mylist = { [1]="foo", [2]="bar", [3]="baz" }:
local mylist = { "foo", "bar", "baz" }

print(mylist[2])          --> bar
        
print(unpack(mylist))      --> foo bar baz 

Remember that Lua expects most tables to count from 1, not from 0.

Iterating a table

To visit only array-portion of a table, use a numeric for loop or ipairs, like the following. The traversal follows the order of the keys, from 1 to the length of the table:

for i = 1, #mytable do
    local v = mytable[i]
    -- do things with the index (i) and value (v)
    print(i, v)
end

for i, v in ipairs(mytable) do
    -- do things with the index (i) and value (v)
    print(i, v)
end

To visit all key-value pairs of a table, including the array-portion, use a for loop with pairs. Note that in this case, the order of traversal is undefined; it may be different each time.

for k, v in pairs(mytable) do
    -- do things with the key (k) and value (v)
    print(k, v)
end

Functions

Functions can be declared in several ways:

-- these are equivalent:
sayhello = function(message)
  print("hello", message)
end

function sayhello(message)
  print("hello", message)
end

-- using the function:
sayhello("me")  -- prints: hello me
sayhello("you") -- prints: hello you

-- replacing the function
sayhello = function(message)
  print("hi", message)
end

sayhello("me")  -- prints: hi me

Functions can zero or more arguments. In Lua, they can also have more than one return value:

function minmax(a, b)
  return math.min(a, b), math.max(a, b)
end
print(minmax(42, 13)) -- prints: 13 42

In Lua, functions are first-class values, just like numbers, strings and tables. That means that functions can take functions as arguments, functions can return other functions as return values, functions can be keys and values in tables. It also means that functions can be created and garbage collected dynamically.

Method-call syntax

A special syntax is available for a table’s member functions that are intended to be used as methods. The use of a colon (:) instead of a period (.) passes the table itself through as the first implicit argument self. This is called method-call syntax:

-- create a table
local t = { 
    -- with one value being a number:
    num = 10, 
    -- and another value being a function:
    -- (note the use of the keyword "self")
    printvalue = function(self)
        print(self.num)
    end,
}

-- or declare/modify it like this:
function t:printvalue()
    print(self.num)
end

-- use the method:
t.printvalue(t) -- prints: 10

There’s nothing really special here except some fancy syntax for convenience; saying t:printvalue() is just the same as saying t.printvalue(t).

Logic and control flow

-- if blocks:
if x == 1 then
  print("one")
  -- as many elseifs as desired can be chained
elseif x == 2 then
  print("two")
elseif x == 3 then
  print("three")
else
  print("many")
end

-- while loops:
x = 10
while x > 0 do
  print(x)
  x = x - 1
end

repeat
  print(x)
  x = x + 1
until x == 10

-- numeric for loop:
-- count from 1 to 10
for i = 1, 10 do 
    print(i) 
end        
-- count 1, 3, 5, 7, 9:
for i = 1, 10, 2 do 
    print(i) 
end
-- count down from 10 to 1:
for i = 10, 1, -1 do 
    print(i) 
end

-- logical operators:
if x == y then print("equal") end
if x ~= y then print("not equal") end

-- combinators are "and", "or" and "not":
if x > 12 and not x >= 20 then print("teen") end

Lexical scoping

If a variable is declared local, it exists for any code that follows it, until the end of that block. (You can tell what a block is by how the code is indented.) Local identifiers are not visible outside the block in which they were declared, but are visible inside sub-blocks. This is called lexical scoping.

If a variable is not declared local, it becomes a global variable, belonging to the entire script. This is a very common cause of bugs, so it is better to use local in nearly all cases. (Also, local variables are more efficient).

function foo(test)
    -- a function body is a new block
    local y = "mylocal"
    if test then
        -- this is a sub-block of the function
        -- so "y" is still visible here
        print(y)  -- prints: mylocal
    end
end

-- this is outside the block in which "local y" occurred,
-- so "y" is not visible here:
print(y)    -- prints: nil

Assigning to a variable that has not been declared locally within the current block will search for that name in parent blocks, recursively, up to the top-level. If the name is found, the assignment is made to that variable. But if the name is still not found, Lua creates a new global instead. Mostly this does what you'd expect, so long as you use local whenever you declare a new variable.

-- an outer variable:
local x = "outside"
print(x) -- prints: outside

-- sub-block uses "local", which does not affect the variable "x" outside:
function safe()
    local x = "inside"
end
safe()
print(x) -- prints: outside

-- sub-block does not use "local", so this updates the variable "x" outside:
function unsafe()
    x = "inside"
end
unsafe()
print(x) -- prints: inside

Closures

Closures arise from the mixed use of lexically scoped local variables, and higher order functions. Any function that makes use of non-local variables effectively keeps those variable references alive within it. An example explains this better:

function make_counter()
    local count = 0
    -- notice that one function returns another
    -- each call to "make_counter()" will allocate and return a newly defined function:
    return function()
        count = count + 1
        print(count)
    end
end

-- call to make_counter() returns a function;
-- and 'captures' the local count as an 'upvalue' specific to it
local c1 = make_counter()
c1()  -- prints: 1
c1()  -- prints: 2
c1()  -- prints: 3

-- another call to make_counter() creates a new function,
-- with a new count upvalue
local c2 = make_counter()
c2()  -- prints: 1
c2()  -- prints: 2

-- the two function's upvalues are independent of each other:
c1()  -- prints: 4

Garbage collection

Objects are never explicitly deleted in Lua (though sometimes resources such as files might have explicit close() methods). When Lua gets to the end of a block, normally it can release any local variables created with it.

However they might still be referenced (e.g. by tables or closures), in which case Lua won’t release the memory until those values are no longer accessible. Most of the time we don’t even need to think about it.

Lua uses an fast incremental garbage collector that runs in the background, which silently recycles the memory for any values to which no more references remain.


Go to part 2.