I'm new to Lua "classes" (metatables) and I have a doubt.
In the following constructor code that I wrote, I declared the variable obj
as local
. But in most examples on the web, this variable is just assigned to without a local
declaration. So in my understanding, it becomes a global variable (not efficient from what I understood). But is there a reason for that?
A = {}
A.__index = A
function A:new(obj_init)
local obj = obj_init or {val = 0}
setmetatable(obj, A)
return obj
end
I also noticed that members of the class can be accessed directly, even from another Lua module:
x = A:new{val = 2}
print(x.val)
But is there a way to make val
a private member? Maybe also using local
?
CodePudding user response:
First, let's look at what these examples you found might have looked like.
Parameters: Implicit locals
function A:new(obj)
obj = obj or {val = 0}
...
end
in this snippet, obj
is a local variable. This is because all function parameters in Lua are local variables. We may rewrite the function as follows to highlight this:
function A:new(...)
local obj = ...
obj = obj or {val = 0}
end
I assume this is what you saw e.g. in the PIL. You probably renamed the parameter to obj_init
, losing the implicit local declaration of obj
.
Global assignment
If you happened to consume particularly bad resources, you might have seen the following:
function A:new(obj_init)
obj = obj_init or {val = 0}
...
end
in this snippet, obj
is indeed a global variable. This is very bad for multiple reasons:
- Efficiency: You are right - excepting pathological cases, global variables are always slower than local variables as global variables are entries in the (hash part of the)
_G
table whereas locals are stored in fast registers of the Lua VM. - Code quality: Global pollution: This constructor now has a side effect: It modifies the global variable
obj
and expects it to not be modified during its execution. This might lead to this function overwriting a global variableobj
and, even worse, you may not yield from a coroutine in the constructor now, because you're dependent on a global state which may not be altered.
Private members
The typical way to implement private table fields in Lua is by convention: You may prefix the field names with an underscore to indicate that these fields may not be modified from outside. Of course programmers are free to circumvent this.
Otherwise, the concept of "private" variables doesn't mesh too well with the scripting language nature of Lua; you can shoehorn full-fledged OOP onto Lua using metatables, but it will be neither idiomatic nor efficient.
Upvalues
The most idiomatic way to implement private members in Lua is to have them be upvalues of closures ("accessors"):
A = {}
A.__index = A
function A:new(obj_init)
local obj = {} -- empty object: only member is private
local val = obj.val
-- note: this does not need `self`, thus no `:` is used;
-- for setters you might want to discard `self` for consistency
function obj.getVal()
return val
end
setmetatable(obj, A)
return obj
end
x = A:new{val = 2}
print(x.getVal())
-- val can not be set from outside (excepting the debug library);
-- it is "private" and only accessible through the getter method
The downside is that all functions accessing your private members will have to be instantiated with each object creation.
Note that even upvalues aren't fully "private" as they may be accessed through the debug library.
debug
library workarounds
The debug
library allows you to inspect the stack. This allows you to tell which method triggered your __index
metamethod. You could thus return different values to different callers. This may be nice for a proof-of-concept to showcase Lua's metaprogramming capabilities, but should not be done in practice as it is very inefficient and hacky.