How to create a simple importable class in Lua?

2.8k Views Asked by At

I'd like to create MyClass class in Lua in a separate file myclass.lua which I can import and use later. It should be working the following way:

local MyClass = require 'myclass'
tab = {1,2,3}
m = MyClass(tab)

However, following the code in Lua docs I can't make it work and am getting errors attempt to call global 'MyClass' (a table value).

The code I have written so far for myclass.lua:

local MyClass = {}
MyClass.__index = MyClass

function MyClass.__init(tab)
    self.tab = tab or {}
    setmetatable({},MyClass)
    return self
end
return MyClass

There is a plethora of examples how to write classes in Lua but I don't think I understand the difference and as a result getting lost in the implementation details. Is there a more or less conventional way to do it?

2

There are 2 best solutions below

1
On BEST ANSWER

In Lua, you cannot usually call a table like you would call a function. For example, this code will produce an error of "attempt to call local 't' (a table value)".

local t = {}
t()

There is a way of making this work by using metatables, however.

local hello = {}
local mt = {} -- The metatable
mt.__call = function ()
  print("Hello!")
end
setmetatable(hello, mt)
hello() -- prints "Hello!"

When you try and call a table as you would a function, Lua first checks to see whether the table has a metatable. If it does, then it tries to call the function in the __call property of that metatable. The first argument to the __call function is the table itself, and subsequent arguments are the arguments that were passed when the table was called as a function. If the table doesn't have a metatable, or the metatable doesn't have a __call function, then an "attempt to call local 't'" error is raised.

Your example code has three problems:

  1. You are trying to use __init instead of __call. Lua doesn't have an __init metamethod.
  2. __call takes different parameters than the ones you are using. The first parameter to the __call function is the table itself. You can either use function MyClass.__call(self, tab), or use the colon syntax, function MyClass:__call(tab), which implicitly adds the self parameter for you. These two syntaxes are functionally identical.
  3. You haven't set a metatable for the MyClass table. While you are setting a metatable for MyClass's objects, that doesn't mean that a metatable is automatically set for MyClass itself.

To fix this, you could do something like the following:

local MyClass = {}
setmetatable(MyClass, MyClass)
MyClass.__index = MyClass

function MyClass:__call(tab)
    local obj = {}
    obj.tab = tab or {}
    setmetatable(obj, MyClass)
    return obj
end

return MyClass

This sets MyClass to use itself as a metatable, which is perfectly valid Lua.

The system of metatables is very flexible, and allows you to have just about any class/object scheme you want. For example, if you want, you can do everything inline.

local MyClass = {}

setmetatable(MyClass, {
    __call = function (class, tab)
        local obj = {}
        obj.tab = tab or {}
        setmetatable(obj, {
            __index = MyClass
        })
        return obj
    end
})

return MyClass

As well as being concise, this also has the advantage that people can't change the class's metamethods if they have access to the class table.

4
On

There is no __init metamethod available for a table. When you do the following:

m = MyClass(tab)

it looks for the MyClass.__call method definition. Just update your myclass.lua as:

local MyClass = {}
MyClass.__index = MyClass

function MyClass:__call(tab)
    self.tab = tab or {}
    setmetatable({},MyClass)
    return self
end

return MyClass