Lua metatables introduction
what are they and how do they work?
If you ever used Lua, then you heard about its tables and the metatable feature. It is a mix of a powerful, simple and yet confusing mechanism for newcomers. Let’s walk through how they work and some examples of how useful they can be. You can also find real use cases by checking my Hammerspoon spoons.
Lua has, beyond its primitive types, only one object-like type called table.
Tables are associative arrays that can have any other type as key (except nil)
and value. The runtime even uses them to load packages. That means a call
io["open"]() works as the usual
io.open(). Here’s a sample of how to
You can also use them as conventional arrays:
Metatables are tables assigned as the meta for another table. We can describe them as analogue to super classes in other object-oriented languages. An even better analogy is that meta tables act as a proxy of the target table.
You can easily assign a metatable to another table using the
getmetatable are standard library functions to
CRUD tables' metatables. You can use both tables in the previous example
as usual, given the metatable has no metamethods. A metatable without
metamethods is an ordinary table. It does not change the behavior of other
tables in any way. Thus metatables need at least one metamethod to do something
A metamethod is a value - usually a function or table - assigned to reserved
key names on a metatable. The runtime looks them up when executing a certain
operation on a table. They all start with two underscore characters
they’re easy to spot.
A good example of a metamethod is the
__index. The default behavior of a table
is to return
nil if a requested key is not set:
The runtime flow is:
When an index is not found on the target table, it falls back to its metatable’s
__index metamethod. If present, Lua uses it to return a value instead of
nil. That means you can create a custom logic for unknown index retrieval:
What Lua tries to do in this case is:
The flow above is by no means authoritative, as I left out a few more nuances to this logic to keep it simple.1 It does cover the most common use cases though, so let’s keep it that way.
__index(table,key)to prevent confusing the
waldotable instance (or any metatable looked up on this loop) with the standard library
Here’s some simple use cases to get used to how metatables work.
__index meta method can be a table, it may serve default values. This
makes it dead simple to prevent checking/hard-coding alternative values
Like the default values case, you can use a function as the
to return a fallback value. This works for any key, not only those set on the
metatable. This allows you to use many fallback tables, or always return an
specific value type. This again contributes to a DRY code:
__index together with the colon operator allows the
implementation of methods. These resemble object-oriented methods thanks to
Lua’s syntactic sugar. You can then create a model-controller pattern between a
table and its metatable:
Beautiful. I’ll dive into the colon operator on a separate post later on.
The example above is a rather naive implementation to keep it simple. It doesn’t prevent direct CRUD operations, non-numerical or out-of-order keys.
Another reason is that Lua by design is extensible. That means it doesn’t
provide as many ways to lock down tables as it offers to extend them. That
doesn’t mean you can’t make things harder to tamper. You can set the
__index functions to block assignment and retrieval. In this case, both
pull methods must use rawset and rawget to bypass them.
If you have a strong need to enforce data integrity then you should consider using an userdata value. These are defined using the C API.
I’ve only scratched the surface of Lua’s metatables. There’s dozens of supported metamethods. Yet the indexing ones are more than enough to show the power of this mechanism. They are the main drive that makes Lua excellent to extend and prototype fast. This alone explains why the gaming industry has a wide use for Lua.