Comprehensive Windows framework to develop in Lua

Tutorial : Learn programming with Luart (Part 3)

By Samir Tine, published on December 2021

Thanks for taking the time to read the first and second part of this tutorial on learning programming with Luart. Now we'll talk about complex Lua types. Let's get started!

The table type

If you were wondering what a table in a programming language did, then you're not alone! Lua tables are similar to real tables in everyday life: on a table, you can put plates, forks, glasses, etc. In Lua, however, you will find other values: strings, numbers, Bolsaeans, and even other tables. We're not talking about furniture here, but containers. In Lua, a table is a container, which means that it's a type of value that stores other values.

Do you remember the Lua runtime library ? I had hinted at a real library (and now we know of two aisles: sys and string). These aisles are actually Lua tables, that contains functions. As you can see, tables are a simple way to store and organize values. Here is an example that shows how to create a table :

print({ "orange", "banana", "apple" })

In this example, a table that contains 3 strings is created using braces. Braces in Lua means the creation of a new table. Lua will print to the screen "table: XXXXX" (where XXXX is an hexadecimal value). The content of the table will not be printed, because they are contained inside that table (remember a table is a container !). The second line will print the beep value inside the sys table (remember the dot !), and as you already know it's a function value. To access table content, we need to use the dot and the name of the wanted value. That means that every value stored in a table has a name.

Lua tables are dynamic : it's possible to add or remove values inside tables at anytime. The following lines will add named values inside the sys table : = 25
sys.month = 12
sys.year = 2021
sys.message = "Happy Christmas !"

In this example, we add named values to the sys table, from the Luart runtime library. A table "named value" is a field in Lua. In this case, we are adding 4 fields to the sys table. A more convenient way to store the Christmas date in the sys table is to use a single sys field like this : It feels natural, but as you can see, the "=" means to assign the right value to the field at the left. = { 25, 12, 2021 }
The field will now contain a table with three number values. But..wait a to access those values ? What is the name of the field ? In this case, when no field name is provided, Lua assign an integer number that starts from 1 :

print([1])     -- will print 25, the first element in the table
print([2])     -- will print 12, the second element in the table
print([3])     -- will print 2021, the third and so on...
In fact, Lua tables are very powerful containers and use a generic key/value mechanism : each value in the table is stored using a key. That key can be any other Lua value : a string, a boolean, a number, a function, or even an other table. To get or set a table element the universal method is to use the [key] notation :

    sys.human = { }               -- sys.human is now an empty table
    sys.human["gender"] = "male"  -- creates a new key "human" inside that table with a "male" value
    sys.human["age"] = 26         -- creates a new key "age" with a number value of 26
    sys.human[1] = true           -- creates a new key 1 (a number) with a true boolean value
When the key of a table element is a string, and that string begins with a character or a "_", that key is considered as a field and only in that case you can use the field notation to access table elements :

    print(sys.human)                -- prints "table: XXXXX"
    print(sys.human.gender)         -- prints "male"
    print(sys.human.age)            -- prints "26"
    print(sys.human.1)              -- error ! "1" is not a valid field
    print(sys.human.[1])            -- Yes ! prints correctly "true"
    print(           -- prints "nil", the table sys.human does not contain "name" key ! 
Please not that indexing a table (using [ ] brackets or using the field notation) with a key that is not contained, returns a nil value. It makes sense after all, you looked for a something that does not exist. Rather than throwing an error, Lua informs you with the nil value that the key is not valid. Let's go further with using the nil value with tables :

    sys.humain[nil] = "something"   -- error ! a nil key is not a valid key !
    sys.humain.gender = nil         -- change the element with key "gender" from "male" to nil
    print(sys.human.gender)         -- prints nil ! The "gender" key has been removed from the table !

Using a nil value as a key is not possible, as you may have already guessed. But assigning a nil value will remove the element from the table. I think it's pretty well seen and logical. On a side note, as for strings, tables can be handled with specific functions stored in the "string" aisle of the execution library. See the Lua 5.4 manual for a complete listing.

Well, that's all for the moment ! Just remember that tables are much more powerful than they seem with a degree of customization that you cannot yet imagine and which goes well beyond this tutorial for beginners.

The function type

You already know what a function is, just remember the print() function you already know. A function allows to perform an action based on arguments enclosed in parentheses. There are plenty of functions available in the Luart runtime library to perform various actions (create a window, choose a font, open a file, write to the console ...). Functions in Lua are values in their own right and can be used as table elements, as arguments,... Here is how we create a function in Lua :

function hello()            -- declare a function "hello" which takes no argument
    print("Hello !")        -- prints the message "Hello !" to the screen

hello()         -- calls the hello function

Functions are defined with the special word function. This special word is called a keyword. It is followed by the function name, here "hello", with a pair of parenthesis ( ). There's not much this function can do, so let's add some arguments to make it more useful. What will these arguments be? This brings us to a concept we have not discussed thus far: variables. In reality, a variable is simply a word attached to a Lua value. It is like a table field as we have just seen, but without the table. Here is an example:

function hello(name)            -- declare a function "hello" which takes one argument : the variable "name"
    print("Hello "       -- prints the string "Hello " concatenated with the value represented by the name variable

hello("Sam")    -- calls the hello function with one argument, a string value, printing "Hello Sam" to the screen

Functions can return one or more values. This is done with a new keyword : return, the following example demonstrates it :

function square(number)         -- declare a function "square" which takes one argument : the variable "number"
    return number*number        -- return the result of multiplying the variable "number" by itself

print(square(2))                -- prints the value returned by the "square" function called with the argument 2

Functions are values in Lua, they can for example be assigned to a field in a table :

function add(number1, number2)         
    return number1 + number2

mathematic = { }                    -- create a variable "mathematic" representing a new empty table
mathematic.addition = add           -- add to this table a field "addition" with the add function as a value

print(mathematic.addition(1, 2))    -- print the result of the function mathematic.addition(1, 2)

Assigning a function value to a table key, makes that key callable using the same function value. In the example, the table field "addition" represent the "add" function. You may ask why table field "addition" has a value of "add" and not "add()". The reason is simple : when declaring a function, you declare a variable with the name of the function. Using parenthesis on that variable calls the function. Check the following example :

function add(number1, number2)         
    return number1 + number2

print(add)          -- add is a variable containing a function value : prints "function: XXXXX" to the screen
print(add())        -- error !! (no arguments provided : arguments number1 and number2 are nil)
print(add(1, 2))    -- outputs "3"
Functions are particularly useful. You can reuse code by using functions rather than writing the same instructions repeatedly. They also allow you to better structure your programs. Functions in Lua have other advanced features (table method, multiple return values, anonymous functions, variable number of arguments..) that go beyond this beginner's tutorial. This tutorial on complex types in Lua has now come to an end, since we chose not to cover userdata and thread types, which are more advanced concepts.

In the next part of this tutorial, we will discuss flow control in Lua, to introduce some logic in your programs.