Home > database >  local object in lua class constructor?
local object in lua class constructor?

Time:11-13

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 variable obj 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.

  • Related