🎓 A1Learner
🔍
0 of 52 lessons completed

Lua Weak Tables

A weak table is a table whose keys or values do not keep objects alive for the collector. Its entries vanish once nothing else refers to the key or the value.

Weak keys drop a registry entry

The registry below uses __mode "k". Its only key is built inside a helper, so nothing strong refers to it once the helper returns.

local function makeRegistry()
  local registry = {}
  setmetatable(registry, {__mode = "k"})
  local player = {name = "Ada"}
  registry[player] = 100
  return registry
end
local function count(t)
  local n = 0
  for k in pairs(t) do
    n = n + 1
  end
  return n
end
local registry = makeRegistry()
print(count(registry))
collectgarbage("collect")
print(count(registry))

Weak values drop a cache entry

The cache below uses __mode "v". Its item is built inside a helper, so only the weak slot refers to it once the helper returns.

local function makeCache()
  local cache = {}
  setmetatable(cache, {__mode = "v"})
  local item = {name = "sword"}
  cache["sword"] = item
  return cache
end
local cache = makeCache()
print(cache["sword"] ~= nil)
collectgarbage("collect")
print(cache["sword"] == nil)

Weak keys and values together drop an entry

The table below uses __mode "kv". Both its key and its value are built inside a helper, so neither survives the helper call strongly.

local function makeBoth()
  local t = {}
  setmetatable(t, {__mode = "kv"})
  local keyObj = {id = 1}
  local valueObj = {id = 2}
  t[keyObj] = valueObj
  return t
end
local function count(t)
  local n = 0
  for k in pairs(t) do
    n = n + 1
  end
  return n
end
local t = makeBoth()
print(count(t))
collectgarbage("collect")
print(count(t))

Example

local function makeCache()
  local cache = {}
  setmetatable(cache, {__mode = "v"})
  local item = {name = "shield"}
  cache["shield"] = item
  return cache
end
local function makeRegistry()
  local registry = {}
  setmetatable(registry, {__mode = "k"})
  local player = {name = "Ada"}
  registry[player] = 100
  return registry
end
local function count(t)
  local n = 0
  for k in pairs(t) do
    n = n + 1
  end
  return n
end
local cache = makeCache()
local registry = makeRegistry()
print(cache["shield"] ~= nil)
print(count(registry))
collectgarbage("collect")
print(cache["shield"] == nil)
print(count(registry))

Key points

  • Mode: the __mode field of a metatable sets a table to weak keys, weak values, or both.
  • Weak keys: with __mode "k", an entry vanishes once its key object is collected.
  • Weak values: with __mode "v", an entry vanishes once its value object is collected.
  • Both: with __mode "kv", an entry vanishes once either its key or its value is collected.
  • Cache: a weak-valued table makes a safe cache, because dropped values leave the cache by themselves.
  • Registry: a weak-keyed table makes a safe registry, because dropped keys leave the registry by themselves.

In this example, makeCache builds a weak-valued cache and makeRegistry builds a weak-keyed registry, and each helper returns only its table. Before collection the cache slot holds an object and the registry holds one entry. After a full cycle the cache slot is nil and the registry count is zero, so both structures cleaned themselves up.

Note

a weak slot still holds its value until a collection cycle runs, so check the slot after collectgarbage("collect").

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

Which __mode setting makes a cache drop a value once nothing else refers to it?