Lua is a really simple programming language focused on portability and being able to be embedded, so chances are you won’t be using lua directly but as a part of a bigger program that embeds lua and allows you to use it to interface with the program.

Lua is really easy to learn, especially if you already have a programming background which I assume you do in this post as I will just explain how lua does it differently.

I’ve included a list of many concepts, feel free to jump around the table of contents, as a programmer you should be able to easily understand any part to learn what is relevant to you, especially if you already have some lua experience.

Warning

This tutorial is incomplete, notably I did not explain metatables and a few more concepts I wanted to explain. I will eventually edit this post and complete it.

Hello, World!

Let’s start with a Hello, World to begin:

print("Hello, World!")

So far simple, Python programmers will immediately feel familiar.

Comments

Comments are done via double dashes -- anything after it is ignored until the end of the line.

-- This is a comment.
-- Let's print some text.
print("Hey!")

Multiline comments are supported via delimiters of --[[ and ]]

--[[
This is a multiline comment.
It can span multiple lines.
]]

print("Hello, World!")

Primitive Types

We can use the function type to check the type of a value.

The following are the types supported in Lua, more on them later.

-- numbers: type(5) == "number"
5
5.2

-- nil: type(nil) == "nil"
-- This is the empty type, like null in other languages.
-- like None in Python.
nil

-- strings: type("hi") == "string"
"Hello, World!"

-- booleans: type(true) == "boolean"
true
false

-- tables: type({}) == "table"
{}
-- More on tables later.

-- functions: type(function() end) == "function"
function test() end

Variables

Variables are simple:

x = 5
name = "john"
age = 17

Local Variables

One important thing to note is that all variables are global by default

We must explicitly mark variables as local

local name = "andrew"

Variables are by default nil, there is no undefined error:

randomName -- this is simply nil

All global variables are stored in a global table called _G and any variable access is like accessing that table. Locals on the other hand is more optimized internally so generally local variables are faster and you should try to use them more if possible, a common lua pattern is to “cache” the things you will use in a local variable and use that.

local sin = math.sin

print(sin(90))

You avoid a global variable lookup for math and a property access to sin and just access the local.

This type of performance things are small of course but it counts when you need to squeeze the most performance out of it, especially if you are writing something like a game loop.

Multiple Variables

It is allowed to assign multiple variables at the same time:

x, y = 5, 2

print(x) -- 5
print(y) -- 2

local sin, cos = math.sin, math.cos

print(sin(90))
print(cos(90))

Increment & Decrement

Note that Lua does not have any increment/decrement operators that you are used to in other languages like i++, i += 5, etc.

So you always do this the classic way:

num = 5

-- Increment the number by one.
num = num + 1

Strings

Strings can be made in 3 different ways:

Single Quotes:

'Hello, World!'

Double Quotes:

"Hello, World!"

Double Brackets:

[[
Hello, World!

This method allows multiline strings.
Text that spans multiple lines.

Not to be confused with multiline comments, there is no --
]]

And of course the classic escape sequence stuff like \n are supported, like any other language.

Length of a string.

You can get the length of a string in 3 different ways:

With the # operator:

local str = "hello"
print(#str) -- 5

With string.len:

print(string.len(str)) -- 5

With string.len as a metatable method:

print(str:len()) -- 5

More on metatables later.

String concatenation

A unique feature of Lua is the .. operator which is the concatenate operator.

Instead of using + for concatenating strings we use this:

print("Hello " .. "World") -- Hello World

String Conversion

We can convert any value into a readable string with the tostring function:

local str = tostring(nil)
print(str) -- "nil"
print(type(str)) -- "string"

Control Flow

Now let’s take a look at the control flow stuff.

Operators

The operators we would can use are the same stuff like any other language. >, <, >=, <=, ==.

There is and and or rather than && and || that you might be used to in other languages.

Negations are done with not rather than !

Which should all be familiar if you are coming from Python.

An important thing to know is the not equal operator, you are used to != in other languages but Lua does this differently and uses ~= instead, so get used to that.

If Statements

These are also easy:

if true then
  print("Hello, World!")
end

Of course you would usually use a variable:

x = true

if x then
  print("Hello, World!")
end

It is worth nothing that only false and nil are falsy, everythinf else is truthy, including 0, unlike other languages:

if 0 then
  print("This works!")
end

Like any other language we got else

if available then
  doStuff()
else
  doOtherStuff()
end

And multiple branches with elseif

if one then
  doStuff()
elseif two then
  doOtherStuff()
elseif three then
  doTheOtherThing()
else
  doWhatever()
end

While Loops

While loops are the same:

while condition do
  runCode()
end

Repeat Until

Another loop construct is the repeat until:

num = 10

repeat
  num = num - 1
  print("Hello")
until num == 0

Will run the loop until num becomes 0

For Loops

For loops start at an initializer, a destination number and an optional step.

for i = 0, 100 do
  print(i)
end

This prints numbers from 0 to 100

Note that the target is inclusive and unlike other languages the 100 is also printed.

We can add a step to change how numbers move:

for i = 0, 100, 2 do
  print(i)
end

This prints the number and keeps moving by 2 so 0, 2, 4, 6, ...

We can use this to count backwards with a negative number:

for i = 100, 0, -1 do
  print(i)
end

This prints numbers starting from 100 and counting down all the way to 0

Functions

We finally we get to functions. These are also simple:

function sum(x, y)
  return x + y
end

print(5, 2) -- 7
  • You return values with return.
  • Undefined parameters are simply nil there is no argument count errors or anything. Similar to JavaScript’s arguments being undefined

Code After Return is illegal

With the way Lua’s syntax is built, it is illegal to write any code after a return:

function test()
  return 5
  print("This breaks.")
end

In other languages that would be considered “dead code” and that part will simply never run, but in Lua that is invalid syntax and your code won’t run.

This permits the return value to be in a different line:

function sum(x, y)
  return
  x + y
end

print(5, 2) -- 7

This works as expected, our first return did not return anything so it followed the next line for the value.

Returning Multiple Values

It is possible to return multiple values and we can use them with multiple assignment:

function getColor()
  return 255, 150, 20, 180
end

r, g, b, a = getColor()

print(r) -- 255
print(g) -- 150
print(b) -- 20
print(a) -- 180

It is possible to only use the values you need, the rest will just be “discarded”:

r, g = getColor()

print(r) -- 255
print(g) -- 150

The return value of these functions are automatically expanded to multiple input parameters if we pass them to another function:

function sum(x, y)
  return x + y
end

function getValues()
  return 5, 2
end

print(sum(getValues())) -- 7

Function Scopes

Note that the same scoping rules as a variable applies to functions too, currently we’ve been creating global variables with the function, we can mark them local too:

local function stuff()
  return 5
end

This is from the fact that defining functions is basically defining a variable and so you can also do:

test = function()
  return 5
end

local stuff = function()
  return 5
end

Tables

This is the main big thing in Lua and is the most powerful thing in the language.

At a simple glance, they are hash tables, dictionaries, maps, associative arrays or whatever you call them. They just map a key to a value.

Let’s get used to the basic syntax and I will go over more of their functionalities after:

-- Create a table
local user = {
  id = 5
  name = "john"
  age = 17
}

And then accessing the values:


-- Two ways to access the table
print(user.name)    -- "john"
print(user["name"]) -- "john"

-- We usually use the dot access but
-- the second method is useful for dynamic access
key = "name"
print(user[key]) -- "john"

We can also modify the table and add more stuff later as needed:

user.location = "London"
-- Or the second method
user["location"] = "London"

print(user.location) -- "London"

Like variables undefined keys are simply nil:

print(user.randomStuff) -- nil

So by setting a key to nil we are essentially deleting it:

user.location = nil

This key no longer exists, this also applies to when we loop through all keys, it won’t be there.

Looping Through Tables

We can use the pairs function to get an iterator of key-values to a table:

for key, value in pairs(user) do
  print(key .. ": " .. tostring(value))
end

This should print:

id: 5
name: john
age: 17

Tables as Arrays

Lua does not have a specific array type like other languages, instead it reuses the concept of tables to create arrays, with the keys being numbers to represent the array index.

We can create one by making a table with elements like so:

local list = { "apple", "banana", "orange" }

Then we can access indices, keep in mind that in Lua array indices start at 1 instead of the usual 0!

print(list[1]) -- "apple"
print(list[2]) -- "banana"
print(list[3]) -- "orange"

Length of arrays

We can use the # operator to get the length of an array table:

print(#list) -- 3

Looping through arrays

With arrays starting at 1 it should now also make sense why for-loops’ target is inclusive. It makes it less confusing to write loops for arrays:

for i = 1, 3 do
  print(list[i])
end

Or using the # operator:

for i = 1, #list do
  print(list[i])
end

Another way to iterate through arrays is the ipairs function, similar to the pairs we saw for key-values except this returns array indices for us instead of keys:

for i, v in ipairs(list) do
  print(tostring(i) .. ": " .. v)
end

It should print:

1: apple
2: banana
3: orange

Inserting and Removing from an Array

We can use table.insert to add elements into an array:

table.insert(list, "kiwi")

This inserts an element into a new position, now list[4] == "kiwi"

We can also insert into a specific position, anything in that position will be shifted up into the next position:

table.insert(list, 1, "pear")

This adds pear into position 1 the first element, so now the entire list shifts up and the items are now:

1: pear
2: apple
3: banana
4: orange
5: kiwi

We can use table.remove to delete elements:

-- table.remove alone will "pop" the last element and give it to us.
value = table.remove(list)
print(value) -- "kiwi"

kiwi is now also removed from the table.

We can give it a specific position to remove which will cause the table to shift down:

table.remove(list, 1)

That will remove the previously added pear and shift down the elements and so we are back to what we had:

1: apple
2: banana
3: orange

Packing and Unpacking Arrays

We can use table.pack to create a table array from multiple paramaters:

local fruits = table.pack("apple", "banana", "orange")

print(fruits[1]) -- "apple"
print(fruits[2]) -- "banana"
print(fruits[3]) -- "orange"

This is useful if a function returns multiple values and you want to store them in an array:

local function getColor()
  return 255, 150, 20, 180
end

local color = table.pack(getColor())
print(color[1]) -- 255
print(color[2]) -- 150
print(color[3]) -- 20
print(color[4]) -- 180

Then there’s the reverse table.unpack that takes a table array and returns multiple values:

local r, g, b, a = table.unpack(color)

Again you don’t need to store everything, you may discard the values you don’t need:

local r, g = table.unpack(color)

Storing Functions in a Table

You can store functions in a table easily:

local utils = {}

utils.getColor = function()
  return 255, 180, 32, 120
end

But there’s a syntax sugar we can use:

function utils.getColor()
  return 255, 180, 32, 120
end

Then we can just use it normally:

print(utils.getColor())