For those of you who never used Lua but wonder what it could do for you, I would like to summarize what is so cool about it, and why it deserves a place of choice in your bag of tools. This won’t be a tutorial of any sort, but an attempt at piquing your curiosity, so code samples will be kept to a strict minimum, and I will just go over a few things that I particularly enjoy.
Lua is simple
The Lua language is case sensitive and dynamically typed (and duck typed actually), the basic types are numbers (double), strings, and tables. Lua code is interpreted (and internally uses bytecode) and hence can do fancy stuff like executing code that generates itself or running in interactive mode. Lua is also very fast for a scripting language, I could show you benchmarks but the point is that if speed is an issue, you should probably not use scripting in the first place.
Lua is stable
The language didn’t change much since version 5.0 (released in April 2003), the current version is 5.1 (released in February 2006), and the upcoming 5.2 won’t break any compatibility.
Lua is small and embeddable
Lua counts around 30 files of good ol’ C code (without dependencies on other libraries), all open source with an MIT licence, and compiles on anything that moves. You can link it as a library, or just drop it as a unity build in your game’s source code and off you go (you can use “etc/all.c”, just remove “lua.c” from it). So let’s get Lua to say the mandatory “hello world” from within a C++ program:
#include "lua.hpp" int main() { lua_State * L = luaL_newstate(); luaL_openlibs( L ); luaL_dostring( L, "print( 'hello world' )" ); lua_close( L ); }
Newstate and close creates and destroy a Lua virtual machine, openlibs is Lua’s equivalent to linking the CRT, and dostring executes a Lua program. Functions start with either “lua” or “luaL” depending if they are part of the core language or part of the helper functions (think DirectX and D3DX), and every single one requires the state as a first parameter because it is just plain C and we don’t have member functions. Lua won’t store or fetch anything outside its state, so you can have as many around as you fancy, completely isolated from each other.
Lua is extensible
Transmission of values to and from Lua uses a stack, this boilerplate code is the price to pay for dynamic typing. To extend Lua you can for example provide your own functions, but it takes two steps. First, send the function pointer, then tell Lua the name of whatever you just sent. When your function is called, you can extract whatever you want from the stack and push back anything else. The highly technical term “whatever” is a subtle reminder of the dynamic typing going on.
int MyFunc( lua_State * L ) { printf( "lua said : %s\n", lua_tostring(L, 1) ); return 0; } // ... lua_pushcfunction( L, MyFunc ); lua_setglobal( L, "say" ); luaL_dostring( L, "say( 'hello world' )" );
Having a host program is not mandatory for extending Lua, you can compile DLL or equivalents and load them from within Lua. And with the “alien” library you can even hook into dynamic libraries that were not designed for Lua in any way.
And you are not constrained to standard Lua types, you can expose “userdata”, which is some kind of opaque container where you can store anything, and provide Lua with functions for dealing with it.
Lua functions are values
You can put a function in a variable and send it as a parameter. Or even use unnamed functions and create a function from a string containing its source code. This opens the door to lambda, closures, classes and anything your tortuous mind could come up with.
function do_it( func ) func() end do_it( function() print( 'hello world' ) end )
Lua tables = arrays, maps and objects, all at once
A table is a generic container. You can add sequential elements (starting at index 1) and you have an array. You can add name-value pairs and you have a dynamic struct (or a map if you prefer). And of course you can also add functions… and you get objects. The only notation that Lua explicitly provides for handling OOP is the colon operator, it automatically sends the table as the first parameter of the function it contains. A small example will make it clear:
vector = { len = function( vec ) return ( vec.x ^ 2 + vec.y ^ 2 ) ^ 0.5 end } vector.x = 3 -- like a struct vector[ 'y' ] = 4 -- like a map (so the key can be a variable) print( vector:len() )
So that’s an object, but how do you make a class? Well, there is a neat little thing named a metatable, that tells a table what to do when nonexistent items are accessed (amongst other things). When calling a function that is not there, we could simply redirect the search to the class table. So it works just like classes, with inheritance and all, but being all dynamic an instance can change class at runtime… isn’t that sweet? (or plain scary maybe, it depends)
Lua functions are flexible
Being dynamically typed, Lua functions do not have to declare their return type. They can also deal with optional parameters, variable number of arguments, named arguments, and multiple return values. In C++, how many times did you have to implement multiple return values by choosing between passing return parameters by pointer or reference or returning a structure ? In Lua, you can do this :
function foo( size_x, size_y ) size_y = size_y or size_x -- choose size_y if it exists, size_x otherwise if size_x < 0 or size_y < 0 then return 0, 0, "error" end return size_x * size_y, ( size_x + size_y ) * 2 end area, circumference = foo( 10 )
This function returns the area and the circumference of a rectancle, which is a square if the second parameter is not not given. In case of error, it returns zero for both and an error message as third value.
Lua has cooperative multithreading
Cooperative means you won’t get any performance boost out of it, and a bit of the contrary actually. But it allows for a very convenient organization of certain tasks, when you can simply yield out of a loop and resume it back later. And you don’t have to worry about locks, there won’t be any concurrent access anyway. Here is the smallest example I could come up with, it might not be that easy to read, as it uses closures, but it should give you the general idea.
function thread( text, count ) return function() for i = 1, count do print( i, text ) coroutine.yield() end done = ( done or 0 ) + 1 end end t1 = coroutine.create( thread( 'hello', 5 ) ) t2 = coroutine.create( thread( 'world', 3 ) ) repeat coroutine.resume( t1 ) coroutine.resume( t2 ) until done == 2
Lua gotchas
Here is a short list of things that surprised me at first:
- Lua starts counting at 1, and not 0 like you are probably used to.
- In Boolean operations, only “nil” and “false” are false, the number 0 is true.
- The inequality operator is “~=” and not “!=”.
- By default, everything has global scope, even variables defined within functions.
What Lua can do for you me
There is no point in listing Lua use cases, nor to suggest what you might use it for. So here is instead a list of some ways I used Lua.
- Real-time data massaging between robotic sensors and a control application.
- Game GUI description language.
- Turn-based game network protocol and log format.
- Server administration scripts.
- In-game console for tweaking and debugging.
- C++ code template generator.
- XML transformer and generator.
- Visual Studio project parser and makefile generator.
- Data-description language (it looks a bit like JSON).
On top of that, Lua makes a nice language for learning programming, as long as you stick with the basic principles (closures, lambdas and metatables might a bit rough for beginners).
Conclusion
I really hope that if you never used Lua before you are now curious about it, maybe to the point of giving it a shot. Engineering is all about choosing the right tool for the job, and some additional choice cannot be bad. But as always, everything has downsides, and there is no silver bullet. Lua is so flexible that you could grow another language out of it, and you can build most of the features of other languages into it, but if you find yourself mimicking C++ in Lua, are you sure you shouldn’t be using C++ instead? If I was to give an advice about Lua, it would be to use it where it fits, and to try to keep it how it was designed: simple, compact and readable.