1

I'm new to Lua and trying to understand the concept of OOP in Lua. To do so, I've tried creating an object and creating methods and "private variables". My issue is when I try to use "setters" or "getters", it's indicating that my tables are returning NIL which means I'm either having a scoping issue or something else I can't figure out.

The kicker is I'm using an example from an online Lua coding tutorial, and when I run the tutorial it works flawlessly. However, when I run mine, I get NIL or nothing outputs whenever I try to "get" or return a value from one of the member functions.

I'm using a couple of different environments:

  • ZeroBrain
  • Sublime Text
  • Lua for Windows

Do you know why my code is not returning populated tables?

newPlayer = function(n, h, a, r)

  player = {}

  n = n or ""
  h = h or 100
  a = a or 100
  r = r or 0

  function player:getPlayerName() 
    return n 
  end

  function player:getPlayerHealth() 
    return h 
  end

  function player:getPlayerArmor() 
    return a 
  end

  function player:getPlayerRank()
    return r 
  end


  function player:setPlayerName(arg) 
    n = arg 
  end

  function player:setPlayerHealth(arg) 
    h = arg 
  end

  function player:setPlayerArmor(arg) 
    a = arg 
  end

  function player:setPlayerRank(arg) 
    r = arg 
  end

  function player:connect(arg)
    print(string.format(" %s joined" , arg)) 
    end

  return player

end

player1 = newPlayer("John", 100, 100, 1000)
player1.getPlayerName()
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982

2 Answers2

4

Your code does not contain "populated tables" to return.

Your newPlayer function does create a table, and it does return it. It creates a number of functions within that table. But that's all newPlayer does: creates a table and puts some functions in it.

The data accessed by those functions is not part of the table. n, h, a, and r (BTW, please use better variable names) are all local variables. Your inner functions will access the specific stack containing those variables, but the variables themselves will not be magically associated with the table.

Your principle problem is almost certainly with the setters. And it comes from a combination of this:

function player:setPlayerName(arg)

with this:

player1.getPlayerName()

When you create a function using a : character between a table name and the function's name, you are using syntactic sugar for a function which implicitly takes as its first argument a value called self. As the name suggests, this is supposed to represent the object which this function is being called upon. So your function creation code is equivalent to:

function player.setPlayerName(self, arg)

Since you create all of your functions with :, all of your functions take at least one parameter.

The : syntax can also be used when calling such functions. If you did player1:getPlayerName(), this would cause the table you accessed to find the getPlayerName function to be used as the first argument in the function call. So that line would be equivalent to player1.getPlayerName(player1).

Obviously, these two syntaxes are mirrors of one another: functions created with : take a parameter that is expected to refer to the table it is being called on, and functions called with : will be given the table which was accessed to get that function.

But... your code didn't stick to the symmetry. You created the functions with :, but you call them with .

Now, you get functions are able to get away with this because... well, none of your values are actually part of the table. So your get functions just return the local value that they adopted from their creating context.

The set functions pose a problem. See, they take a parameter. But because the function was declared with :, they really take two parameters, the first being the implicit self.

Now, : syntax is just syntactic sugar; it's just a convenient way to do what you could have done yourself. So it is in theory OK to call a function with . even if you created it with :. But if you do so, you must pass the table as the first parameter. Though your code doesn't show it, I strongly suspect you didn't do that.

If you called player1.setPlayerName("foo"), what will happen is that the implicit self parameter will get the value "foo", and the arg parameter will be nil. And you will assign that nil value to the n local variable. So subsequent calls to player1.getPlayerName() will return nil.

Basically, what's going on here is that you're combining two different ways of creating objects in Lua. You stored your private data in a way that external code cannot access (ie: local upvalues), but that data is now no longer part of the table itself. Which means that, although you dutifully create those functions with : syntax to indicate that they take a self table, they never actually use that table. And because they never use the table, it's a lot harder to figure out what's going wrong.

Basically, the key here is to be symmetrical. If you create a function with :, then you should either call it with : or make sure to pass it the object table as the first parameter.


Broadly speaking, the standard way to create private members is by convention, not by forbidding it. That is, you agree not to mess with any members of a table other than those with certain names. Python convention is to pretend that names starting with _ don't exist, and Lua programs sometimes use that.

Upvalues are an interesting solution for private variables, but they do come with problems. If you want to invent a member variable, you have to do it in a centralized place rather than wherever you might need one. Even if the variable is optional, you have to create a named local at the top of the function.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • This coding was an equivalent of a "doodle" so that's why the variables are named so shortly. They represent "name", "health", "armor", and "rank". I'm creating a pseudo-simulation of a player creator or something. – C0d3M0nk3y03 Apr 21 '20 at 05:15
  • I'm guessing I just have to assign "player" to those variables within the function and then I'll be able to return them properly? – C0d3M0nk3y03 Apr 21 '20 at 05:15
  • Could you provide an example of "create private members by convention, not by forbidding it"? Lua is different from C++ where we could at least provide "private" type values to variables. In Lua, that's much harder it seems. – C0d3M0nk3y03 Apr 21 '20 at 05:16
  • @C0d3M0nk3y03: "*I'm guessing I just have to assign "player" to those variables within the function and then I'll be able to return them properly?*" ... what are you talking about? Your problem has nothing to do with what's being "returned"; I ran the code you posted and it "returned" a value just fine. As I explained, your problem is about how you're *calling* the functions, specifically the `set` functions. – Nicol Bolas Apr 21 '20 at 05:51
  • @C0d3M0nk3y03: "*Could you provide an example of "create private members by convention, not by forbidding it"?*" You name your private variables a certain way; it's that simple. Like I said, Python-style is to prefix private variable names with the `_` character. Old-style reverse-Polish notation preferred prefixing them with `m_`. You could prefix them with `my_private_variable_` or whatever you like. It's a naming convention. – Nicol Bolas Apr 21 '20 at 05:53
  • Well, you've been an excellent help, and thank you for being so detailed in your explanations! – C0d3M0nk3y03 Apr 21 '20 at 07:39
0

TLDR of Nicol's answer, see my answer to another question:

  function player:setPlayerArmor(arg) 
    a = arg 
  end

The : syntax is syntactic sugar. It creates an implicit 'self' argument when declared, and when used. If you declare it one way and use it another, the arguments won't be what you're expecting. Say your player has 100 health. Look at this result:

player1.setPlayerHealth(55, 66)
print(player1.getPlayerHealth())
-- will display '66', not '55' because `:` declares implicit 'self' argument

This displays 66 because the setPlayerHealth function has an implicit 'self' parameter because it was declared with :. If you instead called it with the ::

player1:setPlayerHealth(55, 66)
print(player1:getPlayerHealth())
-- will display '55' because `:` passes player1 as self
function player:setHealth1(arg)
  -- implicit 'self' argument refers to player1 when called on player1
end

-- is the same as
function player.setHealth2(self, arg)
  -- with `.` notation, you need to add the 'self' argument explicitly
end

player1.setHealth1(31) -- self argument will be 31 and arg will be nil
player1.setHealth2(32) -- self argument will be 32 and arg will be nil
player1:setHealth1(33) -- self argument will be player1 and arg will be 33
player1:setHealth2(34) -- self argument will be player1 and arg will be 34
Jason Goemaat
  • 28,692
  • 15
  • 86
  • 113