Implementing classes in Lua 5.2

Welcome to the fifth part of these tutorials about embedding Lua 5.2 into a C++ host program. If you have not yet, you may want to read my previous notes on Running Lua 5.2 scripts from C++Passing variables from Lua 5.2 to C++, Calling C++ functions from Lua 5.2, or Creating a Lua 5.2 dynamic link library of C functions.

This time we are going to cover the first steps towards the final goal of these notes — passing objects between Lua 5.2 and C++. In this tutorial we are going to implement the equivalent to a C++ class in Lua, instantiate some objects of that class, and play with them. Notice that Lua does not support classes as it is, so we have to extend Lua to support these structures. We will be paying much attention to the Lua 5.2 Reference Manual and some Lua-Users Sample Code, though this implementation of classes is entirely new from scratch, and thus, probably wrong. Be warned!

Lua classes

Lua does not support classes directly. This means that it does not provide all the mechanisms needed to easily create and manipulate them. However, Lua can be extended to support classes. In particular, Lua tables are good candidates for the classes equivalent. They can store values, including functions that can act as class methods. In addition, as tables support metatables, one may generate the equivalent to C++ classes constructors and destructors. For instance, consider the following Lua code (download link):

-- account table will be account class
Account = {}

-- account class methods
A_methods = {
    setUsername = function (self, username_)
            rawset(rawget(getmetatable(self), "private"),
                "username", username_ or "unnamed")
        end,
    setEmail = function (self, email_)
            rawset(rawget(getmetatable(self), "private"),
                "email", email_ or "undefined")
        end,
    getUsername = function (self)
            return rawget(rawget(getmetatable(self), "private"),
                "username") or "unnamed"
        end,
    getEmail = function (self)
            return rawget(rawget(getmetatable(self), "private"),
                "email") or "undefined"
        end
            }

-- account class does not allow new fields
function Account:new_field(key_, value_)
    print"ERROR: objects of class Account do not accept new fields."
end

-- account class destructor
function Account:destructor()
	print("~Account() called for object ", self)
end

-- account class constructor
function Account:constructor(username_, email_)
    local a = {}
    local a_mt = {__index = A_methods,
                  __newindex = Account.new_field,
                  __gc = Account.destructor,
                  private = {}
                 }
    setmetatable(a, a_mt)
    a:setUsername(username_)
    a:setEmail(email_)

    print("Account() called. Created object ", a)

    return a
end

-- metatable to the class (not an object)
setmetatable(Account,  {__call = Account.constructor})

-- create a new object
a = Account("Alejandro", "a.camara@mydomain.es")

-- print its public elements (none) print"Object fields"
for k,v in pairs(a) do
    print(" ", k, v)
end

-- print its metatable
print"Object metatable:"
for k,v in pairs(getmetatable(a)) do
    print("    ", k, v)
end

-- print its private table
print"Object private table:"
for k,v in pairs(rawget(getmetatable(a), "private")) do
    print("    ", k, v)
end

-- break all references to our object and call the GC
a = nil
collectgarbage()

whose output is

You can run the script invoking the lua interpreter with the path to the script as sole argument:

> C:luainstalldirlua C:luaprojectsdirclass_script.lua

If you append the ‘-i’ flag before the script path, once the script has run you will be left in the Lua interpreter to play with the object it just created:

> C:luainstalldirlua -i C:luaprojectsdirclass_script.lua

this is hidden

Weak encapsulation

The class that is generated with this piece of Lua code has the following characteristics:

  1. It has two private fields, username and email, that are weakly encapsulated.
  2. It has seven methods, a pair of getter/setter for each of its fields, a constructor/destructor pair, and a method called when a new field is to be created.

The private methods are weakly encapsulated, not completely encapsulated, because one stubborn programmer might access the private data of the class with something like this:

rawset(rawget(getmetatable(a), "private"), "username", "1337C0D3R")

Hence, “weakly” means “not at all” in this case. However, the fields are not completely exposed because normal access wont work on it. Lua will return nil in these two cases:

username = a.username
email = a.email

and it wont allow you to modify these fields like this:

a.username = "irresponsiblecoder"

The preferred way of accessing the fields — the hinted way if you allow me — is to use the getter and setter for the fields:

a:setUsername("responsiblecoder")
myusername = a:getUsername()

Notice the colon separating the object name and the method. This is Lua syntactic sugar and it is (almost) equivalent to:

a.setUsername(a, "responsiblecoder")
myusername = a.getUsername(a)

this is hidden

Class constructor and destructor

Now look at the code that creates a new object:

a = Account("Alejandro", "a.camara@mydomain.es")

Account is a table, so a functional-style call — accessing  it with parenthesis instead of brackets — delegates the arguments to the “__call” field of its metatable — in case its value corresponds to a function. Previously, we set this metatable field to the Account.constructor function:

setmetatable(Account, {__call = Account.constructor})

so in the end, the original call is equivalent to:

a = Account.constructor("Alejandro", "a.camara@mydomain.es")

And what does the constructor() function do? Well, many things. First it creates the tables that will held the object and its metatable. The object table is empy, while the metatable is filled with:

  1. The “__index” field with value an external table that contains all the methods of the class. When something tries to access a field that is not present in the object table, Lua will look for it in this methods table. Notice that the methods have always “self” as first argument. That enables the use of a:method() for calling the function as it were the object method.
  2. The “__newindex” field with value an external function that only prints an error. When something tries to create a new field in the object table, this function will take care of it. Since we only print a message, no new fields can be created on the object table — at least following the usual way.
  3. The “__gc” field with value an external function that manages the destruction of the object. When no references are kept for the object table, the garbarge collector will dispose the object. But first, if the user has specified so, will call the function stored in the “__gc” field of its metatable. In our case this just prints some information.
  4. The “private” field with value an empty table. Here will be stored the private values of the class. As commented previously, these values are difficult to access, thus providing a hint to the programmer about its scope and, hopefully, making him/her realize he/she is no supposed to be accessing them but with its getters and setters.

The constructor then sets the metatable of our object table, sets the object private fields using the class methods, and returns the local copy of the object. In Lua you will have the newly created object stored in the variable ‘a’.

And that is it!

We have established the foundation for the next tutorial. Though the classes discussed in this tutorial are quite primitive — no real encapsulation, no discussion about inheritance — they will serve to illustrate some concepts that will be used when passing C++ objects to Lua in a way that Lua can interact with them.

So stay tuned for the next episode of these homemade tutorials about Lua 5.2!

About

View all posts by

2 thoughts on “Implementing classes in Lua 5.2

Leave a Reply

Your email address will not be published. Required fields are marked *