I was working on coding a module for my game when I encountered an error that took me a while to resolve. The issue was that I called a method using a dot (.
) instead of a colon (:
), which led to the error. This got me thinking: Is there a way to throw an error or enforce a check when a method is mistakenly called with a dot instead of a colon?
local obj = {}
function obj:method(...)
assert(self == obj, "Called as \".\" instead of \":\"")
end
Although obj.method(obj)
doesn’t throw.
If you’re using OOP (metatables) this would throw.
Wouldn’t this break in a traditional OOP context since self refers to the class instance itself where as obj represents its metatable? It might be better to check if the metatable of self is equal to obj OR if self is equal to obj to cover the function being called in a static context. An implementation of that could look like this:
assert(getmetatable(self) == obj or self == obj, “Called as "." instead of ":"”).
The reason this works to begin with is because function declarations using “:” simply declare the first parameter of a function to be self. The same goes for calling functions where Lua automatically passes the first argument to the function for you.
Additionally using type checking can also mark issues like the ones described by the author before runtime.
self is the meta table and object is also the metatable. Remeber all OOPs are is just metatables.
So when checking this if you run obj.method
there will be no self automatically passed through, therefore self ~= obj. However running obj:method
does pass self and so the metatables will match.
This is partially true. The problem is that in an actual OOP context your class instance is a different table than the static class you set as a metatable. Which will break the originally provided solution.
local staticClass = {}
function staticClass:new(money)
local classInstance = {money = money}
setmetatable(classInstance, self)
self.__index = self
return classInstance
end
function staticClass:DoTheThing()
assert(self == staticClass or getmetatable(self) == staticClass, "Called incorrectly!")
print(self == staticClass)
print(getmetatable(self) == staticClass)
print(self.money)
end
classInstance = staticClass:new(300)
classInstance:DoTheThing()
--[[
Prints:
false
true
300
]]--
As seen in the example above calling :DoTheThing()
on the class instance does not pass the static classes table but the class instance itself. self == staticClass
will therefore be false as the tables are not the same. What you’re really telling lua with your meta table and the self.__index = self
declaration is that when indexing a method of the classInstance table that isn’t actually defined inside it to check inside the staticClass table instead. If a function is found the first argument passed to it will however not be the table that method was found in but rather the table that you originally attempted to call the function from.
It’s also worth clarifying that simply using a metatable is not OOP. As a very simplified explanation object oriented programming refers to the use of classes and creating instances of them. Writing a function like this:
local obj = {}
function obj:method(...)
assert(self == obj, "Called as \".\" instead of \":\"")
end
is not object oriented programming, it’s simply a way of conveniently accessing obj
using self
inside the function.