Lua is a small programming language from Brazil, and the possibly the only tech that came out of Brazil that made significant impact worldwide.
The main feature distinguishing Lua from other languages is that it's really well adapted to being embedded in existing applications, and it's especially popular for video games (here's just a partial list).
In principle you can embed just about any virtual machine for any existing language like Tcl, Python, JavaScript, or whatever else you like. This tends to be much more complicated than embedding Lua. Nowadays JavaScript is increasingly pushing Lua out of its main niche, but if you want to get into game development or modding, some basic Lua is still an useful skill to have. As we explore Lua, you might discover a few reasons why it's losing popularity.
Hello, world!
You probably won't be overly surprised by this code:
print("Hello, World!")
Here's Fibonacci, doesn't look too weird other than the --
for comments and range loop syntax:
-- Fibonacci function
function fib(n)
if n < 3 then
return 1
else
return fib(n - 1) + fib(n - 2)
end
end
for i = 1,30 do
print(fib(i))
end
And the FizzBuzz:
function fizzbuzz(n)
if n % 15 == 0 then
return "FizzBuzz"
elseif n % 5 == 0 then
return "Buzz"
elseif n % 3 == 0 then
return "Fizz"
else
return n
end
end
for i = 1,100 do
print(fizzbuzz(i))
end
Tables
Lua has a single data structure called "table" that serves as both an array/list and a dictionary/hash/object.
Let's see how it works in practice:
local x = {"foo", "bar"}
local y = {"foo", "bar"}
print(x)
print(y)
print(x == y)
What we'd naively expect:
{"foo", "bar"}
{"foo", "bar"}
true
However, what we instead get is:
table: 0x7fb9cee04080
table: 0x7fb9cee040e0
false
That's right! Lua does not have equality working on complex types (the same blight shared by JavaScript), and it doesn't even have builtin console.log
.
Let's write our own inspect
Well, it's not overly difficult to write our own inspect. It's not amazing, doesn't do any pretty-printing, and it can get into infinite loops if data links to itself, and so on, but it should serve our purposes for now.
function inspect(value)
if type(value) == "table" then
local result = ""
for k, v in pairs(value) do
if result ~= "" then
result = result .. ", "
end
result = result .. tostring(k) .. "=" .. inspect(v)
end
return "{" .. result .. "}"
else
return tostring(value)
end
end
local x = {"foo", "bar"}
local y = {name="Bob", surname="Ross", age=52}
print(inspect(x))
print(inspect(y))
Which gets us:
{1=foo, 2=bar}
{age=52, name=Bob, surname=Ross}
What did we learn?
-
type(value)
returns type of whatever we pass - which is"table"
for most complex types - strings can be concatenated with
..
, there's no string interpolation -
!=
is spelled~=
- order of keys in a table is not preserved
- array numbering starts from 1!
That last one might be a bit of a shock. Back in the days, programming languages were split between 0-based and 1-based indexing. Lua is about the last remnant of these times, 0-based indexing having won.
By the way Perl hilariously had $[
which was a special variable determining array indexing, which you could set to 42 for all it cared. They removed this feature at some point. It was actually not completely insane, it was designed to help porting awk scripts to Perl. Maybe I'll get to that story at some point.
Unicode
Let's see how well Lua deals with Unicode:
a = "Hello"
b = "Żółw"
c = "💩"
print(a:lower())
print(b:upper())
print(#a)
print(#b)
print(#c)
And as it turns out, extremely poorly:
hello
ŻółW
5
7
4
Unfortunately :lower()
and :upper()
don't know anything about Unicode, and #
returns number of bytes, not length of the string (string.len(a)
is just like #a
, returning number of bytes).
Should you use Lua?
Honestly for new programs, not really, but it's still worth knowing the basics if you're interested in game development. It still has significant presence in game scripting. As you've seen, even doing very simple things we kept running into problems due to weaknesses of the language.
Lua also seems to have significant issues with community fragmentation. High-performance LuaJIT implementation only supporting fairly old version of Lua 5.1, while the main language moved on to 5.3 already. As Lua code tends to be embedded in some engine (usually game engine), a lot of code depends on various functionality provided by the engine and won't run elsewhere. LuaRocks has 3000 packages, which is tiny compared with 130k ruby gems, or 1.3M npm packages, even if all the rocks ran on every Lua, which they don't.
Right now Lua looks like a language on the way out, but things could still turn around. And unlike most other software - video games see use decades after their release, and with them their Lua code.
Code
All code examples for the series will be in this repository.
Top comments (11)
Thank you for writing!
Lua is a simple little scripting language for embedding. I'm sure Lua is faster and consumes less memory than any other scripting language. To be so lightweight it was designed to be as simple as possible. That's why it doesn't compare objects deeply like python - to not be so slow, that's why it has poor unicode support - don't pack it if you don't need it, if you need it - use a library.
It's a pity that you didn't point to good parts of it.
Indexing from 1 - I was shocked to realize this is possible, it's like a language made for humans!
If I add string and number - I have number "1" + 2 = 3, if I concat numbers I'll get string, after JavaScript this feels ingeniously
Syntax - isn't it look nice?
for i = 1, 10
, instead of just copying C style, no redundant parentheses.Function can return multiple values, like in Rust or Go, without creating temporary array.
Table - Occam's razor in action, just store all in one, why to choose.
I agree that preserving key order is sometimes useful, but it's not for free, and Lua was designed to be fast.
Prototyping with metatables is not only transparent and easy to grasp, but also has more powers than prototypes in JS, it was capable of what Proxy in JS can do long before Proxy appeared in JS.
nil
is awesome to! While JS hasnull
,undefined
,delete
keyword to unset a value, Lua has a singlenil
for every case. It's awkward only when we want to update database record with NULL, then need to use special NULL value exposed by library, or when encoding JSON, but otherwise having one choice is a win.Lua allows to change current global environment, to call a function within custom environment, with one simple function call
setfenv
, is there something alike in any other scripting language?Lua has special functions to access local variables and "closured" variables (called upvalues) of functions. Mostly useless feature, but unique, isn't it? So it's possible to write debugger of Lua right inside of Lua program!
To conclude, Lua is probably most performant, most lightweight, most simple scripting language, few hours is enough to learn Lua, 1-2 days is enough to learn it absolutely in every aspect. Language with nice syntax and more powerful than JS at least was 5 years ago.
You're definitely wrong about one of these things, as official Lua is actually very slow compared with similar languages.
LuaJIT is quite fast, but it's many versions behind official Lua, so you get ecosystem fragmentation, with some people using official version and some people using LuaJIT version, and code not being compatible.
And even if it was fast, I don't think it's worth spending time on language that won't even
print(a_table)
or==
two tables. Programmer time is extremely valuable.Lua isn't dead yet, and it could fix a lot of such issues and call it Lua 6 or something, but nothing suggests that it has any interest in doing so.
By the way lack of Unicode support issue is a massive pain point for game developers, who are Lua's main audience. Pretty much all games are released in multiple languages, so they must support Unicode, so Lua not supporting it is a huge problem. See this for example.
It's a random article where person complains on syntax, they don't say anything about huge problem of installing lua ut8 which is not included to Lua itself, and in the end they say c# shines.
How it's even possible to complain on
--
comments? It looks cool.What I'm to say, Lua has strong sides, has it's niche, it is easy to embed, fast, lightweight, simple. None of these you mentioned in the post except for easy to embed. It must be very fun to explore new languages, but what's the point if you aren't trying to see where they shine at?
openresty.com/en/
I'm not interesting at games at all, I'm very interesting in web platform, and OpenResty is what I've been playing with (years ago). Last release was couple of week ago - it's still alive!
This is Nginx + LuaJIT and it powers high-loaded web sites, here are some stats: https://www.wappalyzer.com/technologies/web-servers/openresty?utm_source=popup&utm_medium=extension&utm_campaign=wappalyzer
Comparing objects deeply violates "fast", programmer will try to find a better way to track changes, including unicode is essential this days, but still, not including it makes Lua bit more lightweigth. Lua doesn't act like Ruby because it's not a goal, it makes no sense to follow Ruby decisions in the area where Lua shines.
It's worth noting that neovim can be configured in lua , and uses it somewhat as a replacement for vimscript.
Yeah, nowadays most programs use an existing language for scripting instead of creating their own.
Imagine using
#
on a unicode string instead ofutf8.len
and then complaining that it doesn't work...Because it works in all the good languages? Anyway, most lua platforms (LuaJIT, any Lua 5.2 and earlier - most games just have whichever Lua was there at release and you cant' upgrade it) don't even have
utf8.len
, and Lua 5.3 which gotutf8.len
still can't do anything else like uppercasing unicode.No clue what you mean with that
Then they probably don't need it. There's no point in a game having unicode support when the engine is from two centuries ago and doesn't support more than ascii anyway.
Yes, that's a good thing.
Worth noting that Elixir is also a programming language created by a Brazilian.