O'Reilly logo

Creating Solid APIs with Lua by Tyler Neylon

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4. Making Your API Classy

Often, the functions in an API can be broken down into a few categories. Suppose, for instance, that a library allows you to manage both image files and movie files. In cases like this, it’s natural to use a class to represent each conceptual object or task being performed by a subset of the interface—for example, we could have an Image class and a Movie class. This chapter covers common methods of defining classes in Lua; implementing inheritance; and defining custom, class-like userdata types in C.

EatyGuy Version 6: Writing a Class in Lua

Most of Lua’s design is simple and transparent, including the building blocks of Lua’s class system. Nonetheless, Lua does ask for some depth of thought when it’s time to implement classes and class inheritance. This is due to the fact that classes in Lua are not directly supported by the language; rather, they are enabled by giving coders the low-level pieces needed to be assembled into class mechanics. Because of this, I’ll cover Lua classes in more detail than I’ve previously covered other language features.

In this chapter, we add enemies to EatyGuy. Let’s call them baddies because it sounds more fun. This is a great opportunity to implement a class–subclass relationship: the player and baddies can share a common superclass that we’ll call Character, whereas the baddy-specific behavior will live in subclass called Baddy. As is common in some languages, we’ll capitalize the names of classes and class-like userdata. All other names we’ll keep in lowercase. Let’s also use the word method to refer to functions that are defined as part of a class.

A Common Code Pattern for Lua Classes

Because Lua provides only low-level devices from which coders build classes, different class implementations are possible. Let’s use a code pattern that is simple yet works well with single inheritance. Here’s a bare-bones example:

-- MyClass.lua

MyClass = {}

function MyClass:new(obj)
  obj = obj or {}                 -- Line 1.
  self.__index = self             -- Line 2.
  return setmetatable(obj, self)  -- Line 3.
end

function MyClass:method()
  print('Hi from ' .. self.name)
end

return MyClass

You could use this class in another Lua file by running the following code:

local MyClass = require 'MyClass'

local obj = MyClass:new({name = 'Winnifred'})
obj:method()  --> Prints out 'Hi from Winnifred'.

Because you’re an astute reader, you probably noticed that these functions are both defined with, and called with, a colon before the function name rather than a period. This colon notation is syntactic sugar for receiving or sending in a special first parameter called self. I’ll explain this notation and then explain MyClass in more detail.

Suppose that you define a function as a member of a table like so:

function atable:amethod(a, b, c)
  -- (function body here)
end

The colon indicates that the function accepts an implicit first parameter called self, followed by any explicitly listed parameters. The preceding example code has the same effect as this more verbose code:

function atable.amethod(self, a, b, c)
  -- (function body here)
end

Now, instead of defining a function, suppose that you’re calling a function on a table:

anothertable:amethod(10, 20)

In this case, the colon syntax means that the value just before the colon will be sent in as an inserted first parameter to the function. So, the preceding method call is the same as this longer version:

anothertable.amethod(anothertable, 10 20)

These two mechanics work together nicely, allowing class methods to access the class instance on which they’re being operated.

Now that you understand colon syntax, let’s see what else is going on in the MyClass example. The most interesting piece is the constructor—that is, the new() method. Let’s understand the actions it takes by stepping through this call:

local obj = MyClass:new({name = 'Winnifred'})

The new() method’s first explicit parameter is obj, and the first line of new() is this:

obj = obj or {}  -- Line 1.

If the constructor were called with no parameters, obj would have a nil value, which is falsy. This first line ensures that obj is an empty table in that case. In general, we’d like obj to be some kind of table, and it will gain status as an instance of MyClass over the next two lines. Here’s the second line of the constructor:

self.__index = self  -- Line 2.

Because new() was called with MyClass immediately before the colon, the value of self is MyClass, so that the line effectively takes this step:

MyClass.__index = MyClass

Here’s the third line:

return setmetatable(obj, self)  -- Line 3.

This makes MyClass the metatable of obj. Any key lookups on obj will fall back to key lookups on the table found in obj’s metatable’s __index value. Temporarily ignoring the case of falsy values, we could write this behavior like so:

x = obj.key or getmetatable(obj).__index.key

In Lua, this expression is essentially executed for you when you simply write obj.key; and Lua is smart enough to evaluate the expression to false if that happens to be the direct value of obj.key. As a result, this method call

obj:method()

works as desired because it’s the same as this one:

fn = obj.method or MyClass.method
fn(obj)  -- And this is the same as MyClass.method(obj).

The main mechanism is that failed key lookups result in a secondary lookup if the table has a metatable with an __index value. In fact, you can chain this delegation process through a series of metatables. We’ll see this chaining device later in this chapter when we discuss class inheritance.

The Character Class Example

Now that we’ve seen the basics of implementing a class in Lua, let’s add one to our blossoming game engine. A character will be any entity that takes up one grid space, such as the player or a baddy, and that may move around the screen at regular time intervals.

Following is the code for Character.lua, but I won’t explain it in detail because it’s all extracted from the previous version of our EatyGuy Lua file. At first, it might feel as if I’m simply moving code around rather than improving things. The improvement comes with the reuse of this code that we’ll see when baddies are added later in this chapter. Here’s Character.lua:

-- Character.lua
--
-- A class to capture behavior of general
-- characters.
--
Character = {}
function Character:new(c)
  c = c or {}
  self.__index = self
  return setmetatable(c, self)
end

function Character:can_move_in_dir(dir, grid)
  local p = self.pos
  local gx, gy = p[1] + dir[1], p[2] + dir[2]
  return (grid[gx] and grid[gx][gy]), {gx, gy}
end

function Character:move_if_possible(grid)
  -- Try to change direction; if we can't, next_dir will take
  -- effect at a corner where we can turn in that direction.

  if self:can_move_in_dir(self.next_dir, grid) then
    self.dir = self.next_dir
  end

  -- Move in direction self.dir if possible.
  local can_move, new_pos = self:can_move_in_dir(self.dir, grid)
  if can_move then
    self.old_pos = self.pos  -- Save the old position.
    self.pos     = new_pos
  end
end

function Character:draw(grid)
  -- Draw the character.
  local color = self.color or 3  -- Default to yellow.
  set_color('b', color)
  set_color('f', 0)  -- Black foreground.
  local x = 2 * self.pos[1]
  local y =     self.pos[2]
  set_pos(x, y)
  io.write(self.chars)
  io.flush()

  -- Erase the old character pos if appropriate.
  if self.old_pos then
    local op = self.old_pos
    local x  = 2 * op[1]
    local y  =     op[2]
    set_pos(x, y)
    set_color('f', 7)  -- White foreground.
    set_color('b', 0)  -- Black background.
    io.write(grid[op[1]][op[2]])
    io.flush()
    self.old_pos = nil
  end
end

return Character

To make this class available to the Lua game code, the two lines that follow are added to eatyguy6.c immediately after util.lua is loaded. The file eatyguy6.c is otherwise a copy of eatyguy5.c:

luaL_dofile(L, "Character.lua");
lua_setglobal(L, "Character");

In pulling out the Character functionality, we can create the file eatyguy6.lua with a few changes. The player variable will become an instance of Character. I’ve omitted several sections of code, which are identical to the previous version, and used a bold font to highlight the changes to EatyGuy’s Lua code:

-- Changed parts of eatyguy6.lua

...

-- Globals.

local percent_extra_paths = 15
local grid                = nil  -- grid[x][y]: falsy = wall.
local grid_w, grid_h      = nil, nil
local player = Character:new({pos      = {1, 1},
                              dir      = {1, 0},
                              next_dir = {1, 0}})

...

-- The can_move_in_dir() function is removed.

...

local function update(state)
  ...

  if state.clock < next_move_time then return end
  next_move_time = next_move_time + move_delta

  -- It's been at least move_delta seconds since the last
  -- time things moved, so let's move them now!
  player:move_if_possible(grid)
end

local function draw(clock)

  ...

  -- framekey switches between 1 & 2; basic sprite animation.
  local framekey = math.floor(clock / anim_timestep) % 2 + 1
  player.chars   = draw_data[dirkey][framekey]

  -- Draw the player and baddies.
  player:draw(grid)
end

...

For the most part, the extraction either replaces code with a method call, or removes a function altogether in the case of can_move_in_dir().

The end result is a surprisingly small game file—eatyguy6.lua is only 127 lines long—for a pseudographical, procedurally generated interactive game, albeit a simple one.

EatyGuy Version 7: Class Inheritance

At long last, we’re ready to give EatyGuy a reason for all that running around: Let’s add some baddies!

In this section, I’ll cover the mechanics of inheritance while implementing an example subclass of Character called Baddy. There are two common ways that subclassing can appear in an API. You, the API writer, might want to subclass your own objects in order to offer related but slightly different interfaces. For example, suppose that you’ve implemented an Image class. You might also choose to implement an AnimatedImage subclass that adds functionality for working with animated images.

In the case of EatyGuy, there is already some separation between what can be considered a text-based game engine that is providing an API—with functions like set_color() and set_pos()—and game-specific code that uses this API. The Character class is in a gray area that could fall on either side of the game/engine divide, but I’ll consider it to be part of the engine. By making Baddy a subclass of Character, we’ll see an example of a user-written subclass of an API-provided superclass.

Subclasses in Lua

Before diving into the Baddy class, let’s take a look at a more generic subclass of the earlier MyClass example. In the simplest case, your subclass won’t require any special per-instance initialization, and will only either add or replace existing methods. Here’s a simple subclass that overrides method():

-- Subclass.lua
--
-- An example subclass of the earlier MyClass example.
--

local MyClass = require 'MyClass'

local Subclass = MyClass:new()

function Subclass:method()
  print('Hi from subclass instance ' .. self.name)
end

return Subclass

The interesting thing here is that Subclass begins life as an instance of MyClass. (In other languages, it’s common for classes to simply define new types in the language, rather than to be values [such as class instances] themselves.) I’ll explain how this works in a moment. Here’s an example usage of Subclass:

-- subclass_usage.lua

local Subclass = require 'Subclass'

local s = Subclass:new({name = 'Gwendolyn'})

s:method()  --> Prints 'Hi from subclass instance Gwendolyn'.

In using the subclass, we give it similar treatment to the superclass, calling its new() method first, followed by a method() call on the resulting instance.

The preceding example works because of the following two connections:

  • Because Subclass is an instance of MyClass, any method of MyClassincluding new()—is also a method of Subclass. So, calling Subclass:new() is the same as calling MyClass.new(Subclass).

  • The new() method on MyClass was written specifically to support this usage pattern. In particular, a call to MyClass.new(Subclass) will ensure that Subclass.__index = Subclass, and that the new instance obj has Subclass as its metatable. So, methods defined on Subclass will be callable on all instances of Subclass, and methods defined on MyClass that are not overridden by Subclass will also be callable on Subclass instances.

The relationship between an instance of Subclass called obj, and the Subclass and MyClass tables is shown in Figure 4-1.

How __index values chain together the obj, Subclass, and MyClass tables.
Figure 4-1. How __index values chain together the obj, Subclass, and MyClass tables

This simple example didn’t cover two common workflows. First, if you’d like to call a superclass’s method that you’ve overridden from a subclass’s method, you can do so by using this syntax:

Superclass.method(self, <other parameters>)

The other common workflow is the case in which your subclass needs its own new() method. The Baddy class will provide an example of this. You also can wrap calls to the superclass’s new() method, but you must do so explicitly because overridden methods in Lua, even constructors, don’t automatically call the versions they override. Such a new() method might look like this:

function Subclass:new(obj)
  obj = Superclass.new(obj)
  -- Subclass-specific setup.
  self.__index = self
  return setmetatable(obj, self)
end

The Baddy Class

EatyGuy will contain baddies that move around at random. The current function that controls character movement is Character:move_if_possible().

We now have a design decision to make: we could either put the movement code for baddies in eatyguy7.lua, or we could put it in a new Lua file devoted to a subclass of Character. One advantage of keeping the code in eatyguy7.lua is that doing so reduces the total number of files in our game, which is good if each file is not too long. On the other hand, it’s useful to decouple chunks of code that conceptually work on different units or perform separate operations. If code is decoupled nicely, it can be easier to maintain when future changes fit well with the separation you’ve chosen, in which case each piece of separated code will most likely be smaller and simpler to work with.

I’ve chosen to create a new file for the new code because this approach minimizes the impact on eatyguy7.lua, and because this makes it easier to focus on the new code for learning purposes. Here are the necessary changes to eatyguy7.lua, compared to the previous version, eatyguy6.lua:

-- eatyguy7.lua

local eatyguy = {}


-- Require modules.

local Baddy = require 'Baddy'


-- Globals.
...
local baddies = {}


-- Internal functions.
...

local function update(state)

  ...
  player:move_if_possible(grid)
  for _, baddy in pairs(baddies) do
    baddy:move_if_possible(grid)
  end
end

local function draw(clock)

  ...

  player:draw(grid)
  for _, baddy in pairs(baddies) do
    baddy:draw(grid)
  end
end

-- Public functions.

function eatyguy.init()

  -- Set up the grid size and pseudorandom number generation.
  grid_w, grid_h = 39, 21
  math.randomseed(os.time())

  -- Set up the baddies.
  local baddy_info = { {color = 1, chars = 'oo', pos = {1, 1}},
                       {color = 2, chars = '@@', pos = {1, 0}},
                       {color = 5, chars = '^^', pos = {0, 1}} }
  for _, info in pairs(baddy_info) do
    info.home = {(grid_w - 1) * info.pos[1] + 1,
                 (grid_h - 1) * info.pos[2] + 1}
    table.insert(baddies, Baddy:new(info))
  end

  -- Build the maze.
  ...

end

In eatyguy.init(), each baddy is assigned a pair of characters, such as @@, which we use for their eyeballs. They are also assigned home positions, beginning in the three corners of the maze that are far from EatyGuy’s initial position at location (1, 1). I consider this to be a small change to the primary Lua file, particularly considering the level of functionality being added.

Next, here’s the code for Baddy.lua, which I’ll explain momentarily:

-- Baddy.lua
--
-- A subclass of Character to capture behavior
-- specific to baddies.
--

local Character = require 'Character'

-- Set a new direction and simultaneously update baddy.next_dir
-- to be a right or left turn from new_dir.
-- This function cannot be seen outside the Baddy module.
local function set_new_dir(baddy, new_dir)
  baddy.dir = new_dir
  local sign = math.random(2) * 2 - 3  -- Either -1 or +1.
  baddy.next_dir = {sign * baddy.dir[2], -sign * baddy.dir[1]}
end

Baddy = Character:new()

-- Set up a new baddy.
-- This expects a table with keys {home, color, chars}.
function Baddy:new(b)
  assert(b)  -- Require a table of initial values.
  b.pos        = b.home
  b.dir        = {-1, 0}
  b.next_dir   = {-1, 0}
  self.__index = self
  return setmetatable(b, self)
end

function Baddy:move_if_possible(grid)
  local deltas = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}

  -- Try to change direction; otherwise the next_dir will take
  -- effect when we're at a turn in that direction.
  if self:can_move_in_dir(self.next_dir, grid) then
    set_new_dir(self, self.next_dir)
  end

  -- Try to move in self.dir; if that doesn't work, randomly
  -- change directions till we can move again.
  local can_move, new_pos = self:can_move_in_dir(self.dir, grid)
  while not can_move do
    set_new_dir(self, deltas[math.random(4)])
    can_move, new_pos = self:can_move_in_dir(self.dir, grid)
  end
  self.old_pos = self.pos  -- Save the old position.
  self.pos     = new_pos
end

return Baddy

Instances of Baddy’s superclass, Character, simply halt as soon as they hit a wall. When a Baddy instance hits a wall, however, it will continue moving in a random new direction. This behavior is different because the Baddy class overrides the move_if_possible() method.

The file Baddy.lua also contains a local function called set_new_dir() that sets self.dir to a given direction and also sets self.next_dir to either a right or left turn away from self.dir. This results in baddies that are capable of turning at any corner while also avoiding 180° turns.

The set_new_dir() function is also an example of a function that is effectively private to a file. Because it’s a local function that isn’t accessible as a value from the Baddy table, Lua code outside of this file has no access to it without resorting to the debug module. If you’d like to have private values or functions that are visible only within a single file, you can declare them as globals with local scope just like set_new_dir().

Figure 4-2 shows us what the game looks like at this point.

A screenshot from eatyguy7, featuring some rather frightening baddies.
Figure 4-2. A screenshot from eatyguy7, featuring some rather frightening baddies

That’s a bit more exciting!

So far, we’ve talked about writing classes and subclasses in Lua, but we haven’t done much in C in this chapter. Let’s change that by talking about the userdata type, which offers C coders unique powers to combine C-based data structures, including arbitrary pointer values, with Lua-facing functionality.

EatyGuy Version 8: Introducing the userdata Type

Almost every Lua value is equally transparent to both C and Lua code. For example, anyone running either C or Lua can see all the keys and values in a table and look up that table’s metatable. This symmetric transparency, however, does not apply to the userdata type, which is specifically tailored to wrap C-side data in opaque, Lua-facing values.

Technically, a userdata instance has no intrinsic Lua-visible data. How can such an opaque object be useful? Because userdata instances can have metatables, and through these metatables they can effectively have methods and fields, just like tables, as well as support operator overloading. (If you want a refresher on metatables, flip back to the section “The Primary Table Functions” in Chapter 3.) In other words, from the Lua side, a userdata can effectively look and feel just like a table, although all that functionality has to come directly from a metatable instead of being built-in.

From C’s perspective, there are actually two types of userdata objects: full and light. A full userdata instance—the default type—corresponds to a block of dynamically allocated memory that’s managed by Lua’s garbage collector. This gives you the power, for example, to wrap an arbitrary C struct in a Lua value. This struct can be passed around to C functions that know nothing of Lua.

A light userdata, on the other hand, simply wraps a single pointer. Light userdata are seen by Lua as direct values, similar to the number type. You are completely responsible for managing the memory pointed to by a light userdatum. In addition, light userdata instances can’t have metatables, making them unsuitable as classes. The example C code that follows will work with the full userdata type.

Let’s see why it’s useful to be able to opaquely wrap C data. Consider the io.open() method that’s part of Lua’s standard library. The following code reveals the type returned by this function:

> f = io.open('myfile.txt')
> print(type(f))  --> Prints out 'userdata'.

Here are two potential follow-up statements that result in errors:

> setmetatable(f, {})          -- Error!
> read = getmetatable(f).read  -- Ok.
> read(f)                      -- Ok, reads file.
> read(42)                     -- Error!

We can’t change the metatable of f, and we can’t call read() on arbitrary values. To be more precise, we can try to do either of those things, but both result in type errors.

What’s going on behind the scenes is that the variable f, which represents a file handle, is internally stored as a FILE pointer in C. Imagine a parallel universe in which, rather than returning a userdata from io.open(), Lua returned a number from io.fopen() and accepted that number as an input to io.fread(). These functions are meant to be analogous to fopen() and fread() in C, so that the return value of io.fopen() is the Lua number with the same numeric value as the corresponding FILE pointer in C. Suddenly, we can make mistakes like this:

> io.fread(1729)  -- Ruh-roh, that's not good.

In this case, the number given is treated as a pointer so that C code can dereference arbitrary memory, resulting in a segfault or a bus error. At a higher level, this violates Lua’s safety. Lua is designed to be safe in the sense that any error, no matter how egregious, will at worst result in a Lua error, and not something worse such as crashing the hosting C app. In other words, if you were to wrap your Lua script in a call via pcall(), it would be guaranteed to not crash. When you write C code that is executed by Lua, it’s your responsibility to uphold this safety by making sure that any error case is surfaced to Lua as a Lua error, rather than crashing the entire program.

The userdata type makes it easier to ensure this safety when working with values in C that need to be tightly controlled in order to remain valid. In the io.open() example, this safety is provided because Lua users can neither change the value of the underlying FILE pointer nor can they create new userdata values without going through the officially supported methods.

Next, I’m going to take a brief detour to explain how Lua loads modules written in C, and then we’ll bring these ideas together in an example module that implements (x, y) coordinate pairs in C, exposing them to Lua as a userdata-based class.

Writing a Lua-Loadable Module in C

So far in this book, the Lua interfaces we’ve created have either been written in Lua or have been explicitly loaded via C code, such as by using the luaL_dofile() function. But there’s another way: you can write a Lua module entirely in C. Lua can use your platform’s dynamic linking library to find a compiled object file and to call a specially named function in that file that loads your C module. On Windows, these object files have the filename extension .dll (dynamic link library), whereas on Linux and macOS, they have the extension .so (shared object).

Suppose that your C module is written in the file cmodule.c. In that case, Lua expects it to contain the function luaopen_cmodule(); this function is called when the following Lua statement is executed:

> cmodule = require 'cmodule'

Here’s a small, valid C module that can be loaded from Lua, but simply returns an empty table:

// cmodule.c

#include "lua.h"
#include <stdio.h>

int luaopen_cmodule(lua_State *L) {
  printf("luaopen_cmodule() was called!\n");
  lua_newtable(L);
  return 1;  // Return the empty table.
}

The module can be compiled like so, depending on your operating system:

On Windows:
$ cc -c cmodule.c -o cmodule.o
$ cc -shared cmodule.o -o cmodule.dll -llua

On macOS:
$ cc cmodule.c -o cmodule.so -undefined dynamic_lookup -I../lua

On Linux:
$ cc cmodule.c -o cmodule.so -I../lua -shared -fpic

The preceding macOS and Linux commands assume that your Lua header files are in the ../lua directory; if that’s not accurate, change the values of the -I flags to the directory containing your Lua header files. You can find more details on this build process at the lua-users.org page on building modules.

After the .so or .dll file is built, you can load it like so:

$ lua
Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> require 'cmodule'
luaopen_cmodule() was called!
table: 0x7fe0b9e00f60

The response from the require() call indicates two things. First, that your custom C function was effectively called despite the Lua interpreter not even knowing that it existed when the interpreter was loaded; second, that the require() call did indeed return a table. If you inspected the contents of this table, you’d find it to be empty.

You might be wondering how the Lua interpreter found your module, given that you didn’t provide any information about it before require() was called. Lua looks for module files in a fixed number of locations. Similar to a shell’s PATH environment variable, Lua keeps this list of locations in a string called package.cpath. Here’s the value of package.cpath on my Mac:

$ lua -e 'print(package.cpath)'
/usr/local/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/loadall.so;
./?.so

This is a list separated by semicolons. Each path in the list can contain zero or more question marks. When require(<module_name>) is called, those question marks are replaced by the <module_name> string. As an example, a call to require 'cmodule' on my Mac performs the search for the module in this order:

  1. It looks for the file /usr/local/lib/lua/5.3/cmodule.so. If that file exists, it attempts to run the function luaopen_cmodule() within it. If that succeeds, the search ends.

  2. If step 1 failed, it looks for the file /usr/local/lib/lua/5.3/loadall.so. If that file is found, Lua attempts to run the function luaopen_cmodule() within it. If that succeeds, the search ends.

  3. If steps 1 and 2 both failed, Lua looks for the file ./cmodule.so. If that file is found, Lua attempts to run the function luaopen_cmodule() within it.

In fact, this is just a subset of the complete search because the require() function also looks for <module_name>.lua files, and keeps track of a cache of previously loaded modules, skipping the search if the module name is found in the cache. If none of the search steps succeed, a Lua error is thrown.

The cmodule.c example is a bit underwhelming in that it doesn’t actually do anything. Let’s take a look at a more interesting example that does do something.

Implementing a Class-Like userdata Type

We’re now ready to implement our own class-like userdata type. The EatyGuy code often uses (x, y) coordinate pairs to indicate either grid positions or directions. It would be convenient if we could perform standard arithmetic operations on these objects as if they were vectors. So, let’s implement this by using Lua’s __add() and __mul() metamethods.

In a burst of unbridled creativity, I’ve decided to use the name Pair for this example class. We could actually implement the Pair class entirely in Lua because it doesn’t strictly require any of the extra power that C offers. However, I’m going to write it in C because I’m using it to illustrate how you can create userdata-based classes in C. In general, when you’re building a class, you have a choice of using a table or a userdata as the type. The need to store C data—especially pointers—is a good reason to base your class on the userdata type rather than on a table. Compared to building a class on the table type, building on the userdata type makes it much more practical to store and safely encapsulate the C values behind your class’s instances. If you don’t need to store C data, basing your class on a table might be less work for you, and it might provide more transparency to coders using your class.

Let’s begin by looking at the setup of Pair.c and the luaopen_Pair() function:

// Pair.c
//
// When this module is loaded, it sets up two tables:
// * Pair, which is the class itself, and.
// * Pair_mt, the metatable of Pair.
// Pair_mt contains metamethods such as __index.
// Pair contains the new() method so the user can create new
// Pairs by using the familiar pattern:
//
// local p = Pair:new{12, 34}.
//

#include "lauxlib.h"


// -- Macros and types --

#define Pair_metatable "Pair"

typedef struct {
  lua_Number x;
  lua_Number y;
} Pair;

...
// The function Pair_new is defined.
// The functions Pair_mt_{index,add_eq,mul} are defined.
// The basics of this omitted code are explained later in this
// chapter; the full source is in this book's github repo.
...

int luaopen_Pair(lua_State *L) {

  // The user may pass in values here,
  // but we'll ignore those values.
  lua_settop(L, 0);

    // stack = []

  // If this metatable already exists, the library is already
  // loaded.
  if (luaL_newmetatable(L, Pair_metatable)) {

    // stack = [mt]

    static struct luaL_Reg metamethods[] = {
      {"__index", Pair_mt_index},
      {"__add",   Pair_mt_add},
      {"__eq",    Pair_mt_eq},
      {"__mul",   Pair_mt_mul},
      {NULL, NULL}
    };
    luaL_setfuncs(L, metamethods, 0);
    lua_pop(L, 1);  // The table is saved in the Lua's registry.

    // stack = []
  }

  static struct luaL_Reg fns[] = {
    {"new", Pair_new},
    {NULL, NULL}
  };

  luaL_newlib(L, fns);  // Push a new table with fns key/vals.

    // stack = [Pair = {new = new}]

  return 1;  // Return the top item, the Pair table.
}

The first new C API function here is luaL_newmetatable(). To describe this function and its companion, luaL_checkudata(), I’d like to first describe the Lua registry. The Lua registry is a special table that is always available to C code but is hidden by default from Lua code. Aside from its hidden nature, it’s a completely normal Lua table. It exists as a place to store a variant of global values that are usable from C but not from Lua.

Table 4-1 describes the two main helper functions for working with metatables on C-defined classes.

Table 4-1. Helper functions with metatables on C classes
C API function Description Stack use
luaL_newmetatable(L, <tname>) Create a new table and push it onto the stack. At the same time, set this new table as the value of the key <tname> in the registry. [–0, +1]
luaL_checkudata(L, <arg>, <tname>) Check that the value at index <arg> in the stack is a userdata, and that this userdata’s metatable is the same as the metatable stored with key <tname> in the registry. If these conditions are all true, then the C pointer value of the userdata is returned; otherwise an error is thrown with an error message about a bad argument type. [–0, +0]

Earlier in this chapter, we saw how the behavior of a Lua class is determined by its metatable. Lua userdata instances are similar—if they have a metatable, that metatable essentially determines to which class the instance belongs. Hence, when C code sets up a new Lua class, it needs to create a new table to act as the metatable for all instances, and it needs a way to refer back to that metatable later on. The creation and registration of this table is done by the luaL_newmetatable() function. In a moment, we’ll see how the companion function luaL_checkudata() can work with metatables created in this manner.

For now, let’s take a look at Table 4-2 and the other two C API functions just introduced.

Table 4-2. luaL_set and luaL_new lib functions
C API function Description Stack use
luaL_setfuncs(L, <fns>, <nup>) Pop <nup> values from the stack, set each function in <fns> as a key/value pair in the table now at the top of the stack. The functions in <fns> are expected to be given as string/C-function pairs using the luaL_Reg struct, and this array is expected to end with a {NULL, NULL} instance. [–<nup>, +0]
luaL_newlib(L, <fns>) Create a new table and populate it with the key/value pairs from <fns>. The functions in <fns> are expected to be in the same format as those given to luaL_setfuncs(). [–0, +1]
Note

This book won’t dive into the details of upvalues, but I’ll mention that the number <nup> handed to luaL_setfuncs() indicates a number of upvalues to be shared by the functions being set. Upvalues are values visible from a closure—that is, values that are visible to a function but that are defined outside that function and are not globals. You can read more about upvalues in the appropriate section of Roberto Ierusalimschy’s book Programming in Lua.

The two C API functions in Table 4-2 are extremely useful when it comes to defining classes. The luaL_newlib() function is perfect for setting up a new table with function values, which is exactly the kind of table you’d want to return when a module is set up via a require() call. When you’re setting up a metatable, however, you’ll want to use luaL_setfuncs() because it can work with tables created by other means, such as via the luaL_newmetatable() function.

Our example luaopen_Pair() function has the following effects:

  • A new table is set up with key/value pairs for the __index(), __add(), __eq(), and __mul() metamethods. This new table is a value in the registry with the string key "Pair".

  • Another table is set up to include the key "new" and whose value is the C function Pair_new().

  • The table just created—the one that includes the "new" key—becomes the return value of the call to require().

This is enough initialization to understand how the following two lines would work, assuming that your Lua interpreter is using the same version of Lua as the Lua version with which you’ve compiled Pair.c:

> Pair = require 'Pair'
> p = Pair:new{1, 2}
Note

If you happen to call require 'Pair' from a Lua interpreter whose version is different from the Lua version in your lua.h, the interpreter might display a version mismatch error. The fix is to change either your interpreter version or your Lua source code version so that they match. (Type lua -v to see your interpreter’s version; the source’s version is near the top of the lua.h file.)

The call to Pair:new() uses curly braces because we’re sending in a single table as the only argument (ignoring the implicit self value). In other words, the call Pair:new{1, 2} is identical to the call Pair:new({1, 2}), but we’re using the shorter syntax. Such a call would result in the C function Pair_new() being called with the stack set to [Pair, {1, 2}], as those are the two arguments sent into the function.

Let’s take a closer look at Pair’s constructor, Pair:new(), as written in C:

// Pair:new(p)
int Pair_new(lua_State *L) {

  // Extract the x, y data from the stack.
  luaL_checktype(L, 2, LUA_TTABLE);
      // stack = [self, p]
  lua_rawgeti(L, 2, 1);  // 2, 1 = idx in stack, idx in table
      // stack = [self, p, p[1]]
  lua_rawgeti(L, 2, 2);
      // stack = [self, p, p[1], p[2]]
  lua_Number x = lua_tonumber(L, -2);  // p[1]
  lua_Number y = lua_tonumber(L, -1);  // p[2]
  lua_settop(L, 0);
      // stack = []

  // Create a Pair instance and set its metatable.
  Pair *pair = (Pair *)lua_newuserdata(L, sizeof(Pair));
      // stack = [pair]
  luaL_getmetatable(L, Pair_metatable);
      // stack = [pair, mt]
  lua_setmetatable(L, 1);
      // stack = [pair]

  // Set up the C data.
  pair->x = x;
  pair->y = y;

  return 1;
}

This function performs two operations. It begins by extracting the x, y values from its arguments and then creates a new userdata instance to hold those values. The C API functions shown in Table 4-3 are used to help extract the x, y values.

Table 4-3. Some C API functions that are useful for working with Lua tables in C
C API function Description Stack use
luaL_checktype(L, <idx>, <type>) Check that the Lua type of the value at stack index <idx> matches the type <type>, which must be one of the C constants beginning with LUA_T and ending with the type name in all-caps (e.g., LUA_TTABLE). If the check fails, an error message is thrown indicating that the value at <arg> was of the wrong type. [–0, +0]
lua_rawgeti(L, <idx>, <n>) For the table at stack index <idx>, get the value of integer key <n>, ignoring any __index() metamethod. Push the received value onto the top of the stack. [–0, +1]
lua_rawseti(L, <idx>, <n>) Pop the value off the top of the stack and set it as the value of integer key <n> in the table at stack index <idx>; this ignores any __newindex() metamethod. [–1, +0]

In our case, the functions in Table 4-3 make it easy to check the validity of the incoming argument, and to extract the values of keys 1 and 2 in the given table.

Next, the Pair:new() constructor needs to set up a new userdata instance and give it Pair’s metatable. The key function is lua_newuserdata(), which allocates memory similar to malloc(), except that Lua’s garbage collector will manage the actual memory for you. Also similar to malloc(), the return value from lua_newuserdata() can be cast to a pointer of your preferred type and henceforth treated as a pointer to whichever C type you’d like to work with. In the case of the Pair class, the C-side type is a struct with fields x and y.

Let’s see how this nascent userdata obtains the proper metatable. Table 4-4 lists the key functions, including a quick review of lua_newuserdata().

Table 4-4. Some C API functions for working with userdata types and metatables
C API function Description Stack use
lua_newuserdata(L, <size>) Create a new userdata object tied to a chunk of newly allocated C memory of size <size>, in bytes. The new memory is managed by Lua and will be deallocated after the corresponding Lua value is no longer referenced. This function returns a pointer to the newly allocated memory. This pointer and its Lua value will be associated for their lifetime, like a pair of mated albatross. [–0, +1]
luaL_getmetatable(L, <tname>) Retrieve the table stored in the registry with key <tname> and push it onto the top of the stack. [–0, +1]
lua_setmetatable(L, <idx>) Pop the table from the top of the stack and set it as the metatable of the value found at stack index <idx>. [–1, +0]

Note that this last function, lua_setmetatable(), can set metatables for more than just tables. In particular, it can set metatables for userdata, which is exactly what Pair:new() does.

The return value of Pair:new() is a new userdata instance that wraps a C struct of type Pair. The x, y values of this C struct are based on the arguments to Pair:new(), and the userdata’s metatable is Pair_mt, the table set up when luaopen_Pair() made its call to luaL_newmetatable(). Thus, the return value of Pair:new() will have all the metamethods (__add(), __eq(), __mul(), and __index()) set up when the Pair module was loaded.

Rather than cover the complete Pair module in detail, let’s simply take a look at one more function in the Pair.c file. The C function Pair_mt_index(), which corresponds with the __index metamethod, enables Pair users to dereference a single Pair instance in multiple ways, like so:

> Pair = require 'Pair'
> p = Pair:new{100, 200}
> p.x  -- p[1] is a synonym for this value.
100
> p[2]  -- p.y is a synonym for this value.
200

Here’s the code for Pair_mt_index():

// Pair_mt:index(key)
// This is used to enable p.x and p.y dereferencing on Pair p.
// This also supports p[1] and p[2] lookups.
int Pair_mt_index(lua_State *L) {

  // Expected: stack = [self, key]
  Pair *pair = (Pair *)luaL_checkudata(L, 1, Pair_metatable);

  // Find the key value as either 1 (x) or 2 (y).
  int key = 0;
  int t = lua_type(L, 2);
  if (t == LUA_TNUMBER) {
    int ok;
    key = (int)lua_tointegerx(L, 2, &ok);
    if (!ok || (key != 1 && key != 2)) key = 0;
  } else if (t == LUA_TSTRING) {
    const char *str_key = lua_tostring(L, 2);
    if (strcmp(str_key, "x") == 0) key = 1;
    if (strcmp(str_key, "y") == 0) key = 2;
  }

  // Push the value.
  if (key) {
    lua_pushnumber(L, key == 1 ? pair->x : pair->y);
  } else {
    lua_pushnil(L);
  }

  return 1;
}

At last, we’ve used the long-anticipated luaL_checkudata() function to simultaneously check that a given argument was of the expected type and convert that value into a C pointer. This code also uses the two C API functions described in Table 4-5.

Table 4-5. C API functions to work with Lua values of unknown types
C API function Description Stack use
lua_type(L, <idx>) Return the type of the value at stack index <idx>. The returned value is a C int that can be compared with the same constants described for the function luaL_checktype() earlier in this chapter (e.g., LUA_TTABLE). [–0, +0]
lua_tointegerx(L, <idx>, <isnum>) Attempt to convert the value at stack index <idx> into a lua_Integer type. If the conversion is successful, the value pointed to by <isnum> is true, and the return value is the converted integer; otherwise, the value pointed to by <isnum> is false. [–0, +0]

Integrating a C Module into Your API

If you’d like a C module to be loaded only upon a user request, the dynamic loading process described earlier will suffice. You also have the option of loading your C module explicitly. For example, these two statements would load the Pair class from C:

// C code similar to the Lua statement "Pair = require 'Pair'".
luaopen_Pair(L);
lua_setglobal(L, "Pair");

To make that code work, you’d need to set up a header file (e.g., Pair.h) that declared the luaopen_Pair() function, and compile Pair.c into a typical object file—that is, Pair.o—as opposed to the shared object file used for dynamic loading. Here’s a typical command line to compile Pair.o:

cc -std=c99 -I../lua -c Pair.c -o Pair.o

All subsequent versions of EatyGuy will be linked with Pair.o. Linking Pair.o into eatyguy8 is as simple as adding the parameter Pair.o to the command line you use to build eatyguy8. As an example, here is a compilation command that works on macOS:

cc -std=c99 -llua -L../lua -I../lua eatyguy8.c Pair.o -o 
    eatyguy8

As an example of how the Pair class can clarify EatyGuy’s code, consider this extract from the previous version of EatyGuy’s Lua file, eatyguy7.lua, with the portions we’re about to change shown in bold:

-- Part of eatyguy7.lua, the previous verison

local function drill_path_from(x, y)
  grid[x][y] = '. '
  local nbor_dirs = get_nbor_dirs(x, y)
  while #nbor_dirs > 0 do
    local dir = table.remove(nbor_dirs, math.random(#nbor_dirs))
    grid[x + dir[1]][y + dir[2]] = '. '
    drill_path_from(x + 2 * dir[1], y + 2 * dir[2])
    nbor_dirs = get_nbor_dirs(x, y, percent_extra_paths)
  end
end

You can change this function to accept a single Pair value call pt, and you can clarify several of its expressions by using Pair values instead of tables. Here is the same function in eatyguy8.lua, with changes in bold:

-- Part of eatyguy8.lua

local function drill_path_from(pt)
  grid[pt.x][pt.y] = '. '
  local nbor_dirs = get_nbor_dirs(pt)
  while #nbor_dirs > 0 do
    local dir = table.remove(nbor_dirs, math.random(#nbor_dirs))
    grid[pt.x + dir.x][pt.y + dir.y] = '. '
    drill_path_from(pt + dir * 2)
    nbor_dirs = get_nbor_dirs(pt, percent_extra_paths)
  end
end

Other changes of a similar nature can be made throughout eatyguy8.lua; the full example file is available in this book’s online repository (including compilation commands in the corresponding makefile). I won’t list all the changes here, because the purpose of this section was to explain C-based classes rather than to focus on changes in EatyGuy.

Making Your Code Debug-Friendly

This chapter taught you how to build classes in either Lua or C. We saw how classes are built with metatables and how inheritance fits into that structure. We also explored the utility of the userdata type, along with several C API functions and code patterns that can maintain Lua’s code safety while arbitrarily extending its functionality.

Albeit a rare and embarrassing event, most coders occasionally find themselves facing a bug that they must understand and defeat. In the long run, there are a number of practices you can learn that will help you both before and after any particular debugging battle. In Chapter 5 (which begins the section on Script Safety), we take a look at some techniques that will help you avoid bugs before they can happen. We also explore other approaches that can assist you in your quest to identify the source of any issues that do come up.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required