Just like the Moon is constantly influencing planet Earth’s ocean tides, it is likely that, at some point, Lua influenced your navigation over the internet.
Today, we’ll embark on an adventure to uncover how this well-rounded scripting language born in Brazil, eclipses its competitors in terms of speed, size, and simplicity, making it a popular choice amongst applications such as Neovim, NGINX, Redis, World of Warcraft, Roblox, Adobe Photoshop Lightroom, and many more enterprise software.
Disk size matters
If someone ever told you that disk size doesn’t matter, I’m sorry to be the one giving you the bad news… but you’ve been lied to. Of course, not everything is lost since knowing how to use it properly does help a ton, however, take a look at this:
Yup. And by the way, it includes the documentation.
Take this tiny size and put it in a simple API, allowing for strong integration with code written in other languages. Mix it with a small memory footprint. Now make it a fast – if not the fastest – interpreted language (which could even surpass C in some cases when using LuaJIT), and POOF! The result you’ll get is the perfect language for embedded systems.
These are the main reasons why Lua is great for configuring software applications and in the gaming industry (most notably for giving users the ability to extend games with their own mods). Still, if Lua is lightweight, easy to embed into an application, versatile, and fast, then why the heck do we live in a world ruled by JavaScript, Ruby, or even Python instead?
Gentle introduction
Being flexible is a great quality in many different scenarios, but as you might already suspect, there’s no such thing as a free lunch.
From Lua’s website:
A fundamental concept in the design of Lua is to provide meta-mechanisms for implementing features, instead of providing a host of features directly in the language. For example, although Lua is not a pure object-oriented language, it does provide meta-mechanisms for implementing classes and inheritance. Lua’s meta-mechanisms bring an economy of concepts and keep the language small, while allowing the semantics to be extended in unconventional ways.
So, to answer the previous section’s question, while the mentioned languages are indeed more bloated, they and their ecosystems provide many out-of-the-box solutions, whereas languages aimed at simplicity tend to transfer complexity management to the developer. Like everything, you should consider the tradeoffs when choosing a technology.
Before we get into our Lua programming survival guide, I want to share 10 notable facts orbiting around the language alongside comments about each one. Hopefully, this keeps you intrigued instead of feeling as if you’re lost floating in space.
1. Array Indexes Start at 1 Instead of 0 (WHAAAT?)
Weird, I agree. But just like Twitter changing its name to X, you’ll get used to it. Eventually. Maybe. Ok, maybe not.
2. There’s a Single Data Structure: Tables
Arrays? Lists? Dictionaries? Hashes? In Lua everything is represented with tables. Don’t worry, we’ll make sense of it soon enough.
3. Global by Default
Variables are global by default, unless explicitly declared as local (and you do want to declare them as local in 99% of cases).
4. First-Class Functions and Closures
This means that functions can be stored in variables, passed to other functions, and returned in functions. Closures are also supported, allowing functions to capture and retain access to local variables from their containing scope.
5. Multiple Return Values
You read it right. Functions in Lua can return multiple values at once! This is somewhat similar to array destructuring in JavaScript. Awesome!
6. Dynamic Typing
Meh. If you like strongly typed languages – although not built-in – at least there are workarounds such as using annotations to help the language server with types it cannot infer, or using the LuaRocks package manager to install teal, which is a typed dialect of Lua similar to TypeScript.
7. No ++
or +=
Operators
Not the end of the world, but can get verbose and annoying pretty fast.
8. Garbage Collected
It uses automatic garbage collection to manage memory, which is common in higher-level languages, but implemented in a lightweight and efficient manner in Lua.
9. Coroutines
It has built-in support for coroutines, enabling cooperative multitasking. Coroutines allow functions to yield and resume execution, providing a way to handle concurrency.
10. Tail Call Optimization
In order to understand recursion, you must first understand recursion. Proper Tail Calls, as they are called by the official docs, allow functions to call themselves recursively without growing the stack.
How hard does it get?
We’re about to find out.
(If the syntax highlighting is broken, you can check it out here instead)
-- Single line comment
--[[
Multi-line
comment
--]]
codeminer = 42 -- This is a global variable
-- While loops
while codeminer < 50 do
codeminer = codeminer + 1 -- No ++ or += operators
end
-- For loops
for i = 1, 5 do
print("Iteration: " .. i) -- String concatenation uses the .. operator
codeminer = codeminer + 1
end
-- Repeat loops
repeat
codeminer = codeminer - 0.5 -- Numbers can be integer or floating point
until codeminer == 42
-- If clauses
if codeminer > 42 then
print 'Hello World' -- You can only omit parenthesis if a function has a SINGLE argument that is a STRING!
elseif codeminer ~= 21 then -- ~= is not equals
print("Double-quotes are also fine") -- Strings are immutable like Python
else
codeminer = nil -- Undefines the variable for garbage collection
codeminer = anUndefinedVariable -- This is not an error, the undefined variable evaluates to nil
end
local aBoolValue = false -- How to make a local variable
-- Only nil and false are falsy; 0 and '' are truthy!
if not aBoolValue then print('it was false') end
-- 'or' and 'and' are short-circuited. This is similar to ternaries in JavaScript
-- In JS, const ans = aBoolValue ? 'truthy' : 'falsy' would be equivalent to:
local ans = aBoolValue and 'truthy' or 'falsy' --> 'falsy'
x, y, z = 1, 2, 3, 4
-- Now x = 1, y = 2, z = 3, and 4 is thrown away.
-- Functions are First-Class, so they can be passed as arguments, returned, or be anonymous
local function makeLog(someLogFunction, myClosureValue) -- Functions also can be local/global
local doubleValue = myClosureValue * 2 -- and support closures
return function(message) someLogFunction(message .. doubleValue) end
end
local printLog = makeLog(print, 21)
printLog('Codeminer') -- prints Codeminer42
-- Functions can return multiple values
local function returnMultiple(first,...) -- and receive multiple arguments
local second, third, _, fifth = ...
return first, second, third, fifth
end
L, u, a, moon = returnMultiple(1, 2, 3, 4, 5, 6, 7, 8, 9)
-- L = 1, u = 2, a = 3, moon = 5
----------------------------------------------------
-- Tables
----------------------------------------------------
--[[
Now, this is where it starts to get interesting.
Tables are Lua's only compound data structure; they are associative arrays.
So we can use them as 'dictionaries' or as 'arrays'.
]]
-- Array-like Tables
local aLuaArray = {"apple", "banana", "cherry"}
local length = #aLuaArray -- Use # to get the size/length of a table
for i = 1, length do
print(aLuaArray[i]) -- Indices start at 1 !!!!!!!!
end
-- Dictionary-like Tables
local aLuaDictionary = {
name = "Edy", -- Dict literals have string keys by default
[3.14] = 'Pi', -- Almost anything can be used as a key, including booleans, functions, etc
['@!#$'] = true, -- Trailing commas are fine
}
print (aLuaDictionary.name) -- String keys can use js-like dot notation
print(aLuaDictionary[3.14]) -- Literal notation. Prints "Pi"
aLuaDictionary['@!#$'] = nil -- Removes this key from the table
aLuaDictionary.myGreatNewKey = {} -- Adds a new key/value pair
for key, val in pairs(aLuaDictionary) do -- Table iteration
print(key, val)
end
This markdown idea was shamelessly stolen adapted from https://learnxinyminutes.com/docs/lua/.
Congratulations! You’re now a certified Lua programmer… Right?
Wrapping it up
If you have read this far (and I sure hope you have, because if you just skipped to this section, then what are you doing with your life, Billy? What caused you to have the attention span of a fruit fly making you soOoOo unable to just sit down and read a freaking blogpost from top to bottom? You always want things your way, the easy way, don’t you, Billy? Always looking for excuses and shortcuts. Hey!! Don’t you DARE looking away, Billy, I’m talking to you! But anyways…), you might remember that Lua is not a pure object-oriented language, but it does provide meta-mechanisms for implementing classes and inheritance.
Well, such feat is achieved through clever usage of Metatables and Metamethods. To some extent, they could be compared to JavaScript’s Prototypes, but actually are not quite the same.
I believe this is a topic that could get out of hand pretty quickly due to its complexity, so it probably deserves its dedicated post. There are also other more advanced topics like sandboxing, calling C functions, protected function calls, assertions, string pattern matching, and more.
Since I still have more practical Lua tips and tricks up my sleeve, a follow-up to this post might happen sooner rather than later, so stay tuned!
Cool Lua stuff
Game Engine
https://love2d.org/
A collection of packages and modules that implements a node.js style API for the luvi/lit runtime
https://github.com/luvit/luvit
A comprehensive, but not exhaustive, translation of upstream ReactJS 17.x into Lua
https://github.com/jsdotlua/react-lua
A Just-In-Time Compiler (JIT) for the Lua programming language
https://luajit.org/
Add additional context to your code and type checking
https://luals.github.io/wiki/annotations/
The compiler for Teal, a typed dialect of Lua
https://github.com/teal-language/tl
References
Programming in Lua by Roberto Ierusalimschy
https://learnxinyminutes.com/docs/lua/
We want to work with you. Check out our "What We Do" section!