little tongue that could
Lua is perhaps my favorite “small language”, low cognitive load and easy to learn and use. It is built into many software such as Redis, NGINX via OpenResty And Wireshark (approx. translation.: and many others). It is also used as a scripting language in games such as World of Warcraft And Roblox via Luau (approx. translation.: and many others). This post is a short declaration of love for the language with some examples of why I like it so much.

Simplicity
Lua has relatively few features and relatively little syntax. For example, there are only 8 types in the language:
nil
boolean
number
string
userdata
(to represent C data structures or blocks of memory on the heap)function
thread
(for coroutines)table
(an associative array and, concurrently, the only built-in data structure)
No need to worry about float
, int
, usize
. No need to worry about differences in arrays, dictionaries and structures. Even the classes here are just tables (table
) with the specified meta tables (metatables
, approx. transl.: think of prototypes like in JavaScript). This simplicity throughout makes Lua easy to learn and use, while providing enough power to perform most of the necessary tasks.
Let’s implement a simple binary search on an array in Lua:
-- однострочные комментарии начинаются с двух тире
function binary_search(array, value)
local low = 1
local high = #array -- # это оператор взятия длины
while low <= high do
-- доступ к библиотечным функциям через глобальную таблицу
local mid = math.floor((low + high) / 2)
local mid_value = array[mid]
if mid_value < value then
low = mid + 1
elseif mid_value > value then
high = mid - 1
else
return mid
end
end
return nil
end
res = binary_search({2, 4, 6, 8, 9}, 6)
print(res)
All of this should be relatively familiar, even if you’ve never encountered Lua before. The only thing that may seem unusual is the keyword local
which is used to declare variables (approx. translation: and not only). They are all global by default, so local
is used to declare a local variable relative to the current scope.
transpilation
Lua is an excellent target for transpilation due to its simplicity and ease of interoperability with C. Therefore, Lua is an excellent choice for domain-specific languages (DSLs) such as Terra, moonscript And Fennel.
As a quick example, here is the same binary search written in MoonScript and Fennel respectively:
Code
binary_search = (array, value) ->
low = 1
high = #array
while low <= high
mid = math.floor((low + high) / 2)
mid_value = array[mid]
if mid_value < value
low = mid + 1
else if mid_value > value
high = mid - 1
else
return mid
return nil
print binary_search {2, 4, 6, 8, 9}, 6
(fn binary-search [array value]
(var low 1)
(var high (length array))
(var ret nil)
(while (<= low high)
(local mid (math.floor (/ (+ low high) 2)))
(local mid-value (. array mid))
(if (< mid-value value) (set low (+ mid 1))
(> mid-value value) (set high (- mid 1))
(do
(set ret mid)
(set low high)))) ; no early returns in Fennel
ret)
(local res (binary-search [2 4 6 8 9] 6))
(print res)
Embeddability
But the true power of the language is that you can embed it just about anywhere – Lua is implemented as a library for a host program like Redis. Traditionally, this was a C program, but now there are many implementations of the Lua virtual machine in different languages, such as Javascript (with Fengari) or Go (with GopherLua). However, one of the most popular implementations is a scripting language. Luau for Roblox game.
Perhaps one of my favorite uses of Lua is HAProxy, which takes us back to the days of Apache + mod_php scripting. Let’s set up a HAProxy configuration that will respond to requests along a specific path with a random prediction:
Code
local function fortune(applet)
local responses = {
{
quote = "The only people who never fail are those who never try.",
author = "Ilka Chase"
},
{
quote = "The mind that is anxious about future events is miserable.",
author = "Seneca"
},
{
quote = "A leader is a dealer in hope.",
author = "Napoleon Bonaparte"
},
{
quote = "Do not wait to strike until the iron is hot; but make it hot by striking.",
author = "William B. Sprague"
},
{
quote = "You have power over your mind - not outside events. Realize this, and you will find strength.",
author = "Marcus Aurelius"
}
}
local response = responses[math.random(#responses)]
local resp = string.format([[
<html>
<body>
<p>%s<br> --%s</p>
</body>
</html>
]], response.quote, response.author)
applet:set_status(200)
applet:add_header("content-length", string.len(resp))
applet:add_header("content-type", "text/html")
applet:start_response()
applet:send(resp)
end
core.register_service("fortune", "http", fortune)
global
lua-load fortune.lua
defaults
retries 3
timeout http-request 10s
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout http-keep-alive 10s
timeout check 10s
frontend fe_main
bind :8080
mode http
http-request use-service lua.fortune if { path /fortune }
And then run it:
$ haproxy -f haproxy.cfg
$ curl localhost:8080/fortune
<html>
<body>
<p>Do not wait to strike until the iron is hot; but make it hot by striking.<br> --William B. Sprague</p>
</body>
</html>
Why do this at all? It’s easy to imagine a situation where you need a little bit of application logic on top of a web server, but you don’t want to write a complete web application for that task. Or you want to extend the functionality of existing applications, for example, add a small request (endpoint) to check the status (healthcheck) of the Redis server:
Code
-- это сторонний форк redis-lua с поддержкой TLS:
local redis = require("redis-tls")
local settings = {
host = "127.0.0.1",
port = 6379,
database = 14,
password = nil,
}
local utils = {
create_client = function(params)
local client = redis.connect(params.host, params.port, 1, false)
if params.password then
client:auth(params.password)
end
return client
end,
}
local function redis_health(applet)
-- pcall как try/catch, принимает функцию и аргументы,
-- и возвращает true/false и результат выполнения функции
local ok, client = pcall(utils.create_client, settings)
if not ok then
local string_resp = '{"ok":false}\n'
applet:set_status(500)
applet:add_header("content-length", string.len(string_resp))
applet:add_header("content-type", "application/json")
applet:start_response()
applet:send(string_resp)
return
end
local resp = client:ping()
local string_resp = string.format('{"ok":%s}\n', resp)
applet:set_status(200)
applet:add_header("content-length", string.len(string_resp))
applet:add_header("content-type", "application/json")
applet:start_response()
applet:send(string_resp)
end
core.register_service("redis_health", "http", redis_health)
global
lua-load redis.lua
frontend fe_main
bind :8080
mode http
http-request use-service lua.redis_health if { path /redis_health }
Redis server is down/down:
$ curl 127.0.0.1:8080/redis_health
{"ok":false}
Redis server is up and running:
$ curl 127.0.0.1:8080/redis_health
{"ok":true}
You can go ahead and use register_action
And register_fetches
(cm. docks) to intercept information about the request, modify it, or add additional authentication capabilities on top of software without a built-in authentication system.
Community
It’s not very big, but it has a lot of great development and many libraries are available through a simple package manager. LuaRocks. From libraries to fast parsing and JSON encoding before additions to the standard library (approx. translation.: or even adding programmable computers in minecraft) – there is something for everyone.
Worthy of special mention Leaf Corcoranwho wrote Lapis is a fantastic little web framework for the OpenResty distribution that powers the LuaRocks website.
Note. translation.: Unlike some communities, there are no small or big scandals or intrigues in Lua (see Rust 👀: ^1, ^2, ^3)
Conclusion
Is there any conclusion? Lua is very good, you can master it over the weekend and start using it to write authorization layers in HAProxy, add-ons for World of Warcraft, games within Roblox, scripts for your window manager, networking or just little libraries that make you a little bit happier.
Note. translation.: and don’t forget about game engines – Love2D, Defold And Raylib integration.
Personally, I would recommend looking at Fennelmentioned above in translation. It’s LISP transpiled to Lua – you get the speed, simplicity, and accessibility of Lua with the flexibility of LISP syntax and macros.
And for those who are just starting their way in programming, I have a promo code HABR23. I won’t say where to apply, otherwise it’s advertising 🙂
By the way, I run a Telegram channel for those interested in learning and understanding everything in IT, join if you haven’t already!