jawo
jawo

Reputation: 896

Delete variable by reference in Lua

I got several objects in several tables. Multiple functions alter and handover the objects to other functions.

Lets say my table is this:

objectTable = {obj1, obj2, obj3}
otherobjTable = {objA, objB, objC, objD}

And let's say these are initialized at the main.lua.

Now, when tracing obj1, it's altered by a function, which alters it and gives an reference to another function, which again, alters it. One step could look like:

function()
   if something then func(obj_1)
   elseif something else then func(obj_2)
   elseif something other then func(obj_3)
   //... and so on...
end

function func(received_Object)
  if something then
    table.insert(received_Object, a value)
  end
  callAnotherFunction(received_Object)
end

function callAnotherFunction(received_Object)
  if input == "Delete it" then
    local name = received_Object.name
    received_Object = nil
    return string.format("%s was deleten", name)
  else
    return false
  end
end

The problem now is, after received_Object = nil, the reference points nil but the object still exists. How can I get it deleted for sure?

Upvotes: 4

Views: 12115

Answers (1)

W.B.
W.B.

Reputation: 5525

In Lua, certain types (like tables) are always passed by reference, while other types (like numbers) are always passed by value.

Moreover, Lua is a language where memory is managed by garbage collector. Garbage collector deletes an object (a table, for instance), when there are no more references (let's call them anchors) to it.

Now this code:

local t = {}
local t1 = {t}
someFunc(t)

creates three anchors to that table. When someFunc passes another that table as an argument to another function, a fourth anchor will be created (in form of that function's local variable/argument).

In order for a garbage collector to sweep the first table, all those references must be gone (either by assigning nil or by going out of scope).

It's important to understand, that when you assign nil to the local t, it does not mean that the table will be deleted. Even less so that all references to that table will be invalidated. It means that you're just freeing this one anchor, which is just one of four at that point.

Possible solution

One possible solution would be to pass around the table holding your object along with the index/key, at which the object is stored:

function func(myTable, myKey)
...
end

Now if in this function you do this:

myTable[myKey] = nil

(and no other anchors get created), the object under the key will have no more references pointing to it and will be marked for sweeping by the garbage collector the next time round. Of course, callAnotherFunction would have to be modified in the same way as well:

callAnotherFunction(myTable, myKey)
...
end

If you perform many operations on that objects within those functions, you may cache it into a local variable to avoid several table lookups. This is ok, as when the function finishes, the anchor will be cleared along with the local variable:

callAnotherFunction(myTable, myKey)
    local myObj = myTable[myKey]
    ...
    if myCondition then myTable[myKey] = nil end
end  --here myObj is no longer valid, so the anchor is gone.

Another solution

Since you can't afford to change your code as much as suggested above, you could implement the following logic:

Create a metatable for tables that hold your objects:

local mt = {
    __newindex = function(tab, key, val)
        --if necessary and possible, perform a check, if the value is in fact object of your type
        val.storeTableReference(tab, key) --you'll have to implement this in your objects
        rawset(tab, key, val);
    end
}

local container1 = setmetatable({}, mt)
local container2 = setmetatable({}, mt)

Now when you insert an object into that table:

container1.obj1 = obj1
container2.obj1 = obj1

each time the __newindex metamethod will invoke obj1.storeTableReference with appropriate references. This function will store those references in (for instance) an internal table.

The only thing that's left to implement is a method for your object that frees those references:

myObj:freeReferences = function()
    for k, v in ipairs(self:tableReferences) do --assuming that's where you store the references
        k[v] = nil
    end
    tableReferences = {} --optional, replaces your reference table with an empty one
end

Now this solution is a bit clumsy, as there are a few things you need to be cautious about:

  • __newindex only get's fired when the key is first created. So container1.obj = obj1 and container1.obj = obj2 would only trigger __newindex on the first assignment. The solution would be to first set the obj key to nil and then to obj2.
  • when you set obj to nil in that table manually (or to another object), you need to make sure that the reference the object stores gets cleared as well.

Upvotes: 8

Related Questions