Language
日本語
English

Caution

JavaScript is disabled in your browser.
This site uses JavaScript for features such as search.
For the best experience, please enable JavaScript before browsing this site.

  1. Home
  2. Lua Dictionary
  3. setmetatable() (Metatables)

setmetatable() (Metatables)

In Lua, metatables allow you to add special behaviors to tables, enabling operator overloading, default value configuration, and object-oriented programming (OOP). Use setmetatable() to attach a metatable to a table and getmetatable() to retrieve it.

Syntax

-- -----------------------------------------------
-- Setting and getting a metatable
-- -----------------------------------------------
setmetatable(table, metatable)   -- attaches a metatable to table (returns table)
getmetatable(table)              -- returns the metatable of table (nil if none)

-- -----------------------------------------------
-- Key metamethods (fields of the metatable)
-- -----------------------------------------------
__index    = table or function   -- called when a missing key is accessed (basis for inheritance)
__newindex = function            -- called when a missing key is assigned
__tostring = function            -- called by tostring() / print()
__add      = function            -- overloads the + operator
__sub      = function            -- overloads the - operator
__eq       = function            -- overloads the == operator
__call     = function            -- called when the table is invoked as a function

-- -----------------------------------------------
-- rawget / rawset (bypass the metatable)
-- -----------------------------------------------
rawget(table, key)               -- retrieves a field without calling __index
rawset(table, key, value)        -- sets a field without calling __newindex

Syntax Reference

Function / FieldDescription
setmetatable(t, mt)Attaches metatable mt to table t. Returns t.
getmetatable(t)Returns the metatable set on table t. Returns nil if none is set.
rawget(t, k)Gets the value of key k in table t directly, without invoking the __index metamethod.
rawset(t, k, v)Sets key k in table t to value v directly, without invoking the __newindex metamethod.
__index (table)Specifies a table to look up when a missing key is accessed. Enables prototype-chain inheritance.
__index (function)Called as function(t, k) when a missing key is accessed. Used for dynamic default value generation.
__newindexCalled as function(t, k, v) when a missing key is assigned. Used for implementing read-only tables, among other things.
__tostringCalled when a table is converted to a string via tostring() or print(). Used for readable object display.
__addCalled as function(a, b) when a table is added with the + operator.
__eqCalled when a table is compared with the == operator. Enables custom equality checks even when the operands are not the same reference.
__callCalled as function(t, ...) when the table is invoked like a function with t(...).

Sample Code

steinsgate_metatable.lua
-- steinsgate_metatable.lua — basic metatable sample
-- Uses Steins;Gate characters to verify the main metatable features

-- -----------------------------------------------
-- 1. __index (table) — prototype inheritance
-- -----------------------------------------------

-- Define shared information as a prototype table
local LabMemberProto = {
    organization = "Future Gadget Laboratory",
    greeting = function(self)
        return self.name .. " is a lab member."
    end,
}

-- Create individual lab member tables and set the prototype as __index
local okabe = setmetatable(
    { name = "Rintaro Okabe", code = "004" },
    { __index = LabMemberProto }
)

local makise = setmetatable(
    { name = "Kurisu Makise", code = "004" },
    { __index = LabMemberProto }
)

print("=== __index (table inheritance) ===")
-- organization does not exist on okabe itself, so it is fetched from the prototype via __index
print(okabe.name .. " belongs to: " .. okabe.organization)
print(makise.name .. " belongs to: " .. makise.organization)
print(okabe:greeting())
print(makise:greeting())
print("")

-- -----------------------------------------------
-- 2. __index (function) — dynamic default values
-- -----------------------------------------------

-- Create a table that returns "unregistered" for any missing key
local member_list = setmetatable({
    mayuri  = "Mayuri Shiina",
    hashida = "Itaru Hashida",
}, {
    __index = function(t, key)
        return "unregistered: " .. key
    end
})

print("=== __index (function) ===")
print("mayuri: " .. member_list.mayuri)
print("suzuha: " .. member_list.suzuha)  -- missing key is handled by __index function
print("daru:   " .. member_list.daru)    -- also unregistered, so the function is called
print("")

-- -----------------------------------------------
-- 3. __newindex — write hook
-- -----------------------------------------------

-- Create a table that logs changes while writing to fields
local log = {}
local watched = setmetatable({}, {
    __newindex = function(t, key, value)
        -- record the change to the log, then write with rawset
        table.insert(log, string.format("  SET %s = %s", key, tostring(value)))
        rawset(t, key, value)
    end
})

print("=== __newindex ===")
watched.experiment = "Time Machine Experiment"
watched.result     = "World Line Divergence: 1.048596"
watched.operator   = "Rintaro Okabe"
print("Change log:")
for _, entry in ipairs(log) do
    print(entry)
end
print("Saved: " .. watched.experiment)
print("")

-- -----------------------------------------------
-- 4. __tostring — customize print() output
-- -----------------------------------------------

-- Define a metatable for readable character display
local CharMeta = {
    __tostring = function(self)
        return string.format("[%s] %s (Lab Member No: %s)",
            self.code, self.name, self.lab_no)
    end
}

local suzuha = setmetatable(
    { name = "Suzuha Amane", code = "007", lab_no = "008" },
    CharMeta
)

print("=== __tostring ===")
print(tostring(suzuha))  -- __tostring is called
print(suzuha)            -- print() internally uses tostring(), so __tostring is called
print("")

-- -----------------------------------------------
-- 5. __add — operator overloading
-- -----------------------------------------------

-- Define the + operator on a vector table
local VecMeta = {
    __add = function(a, b)
        return setmetatable({ x = a.x + b.x, y = a.y + b.y }, getmetatable(a))
    end,
    __tostring = function(v)
        return string.format("Vec(%g, %g)", v.x, v.y)
    end
}

local pos_okabe  = setmetatable({ x = 3, y = 5 }, VecMeta)
local pos_makise = setmetatable({ x = 1, y = 2 }, VecMeta)
local pos_sum    = pos_okabe + pos_makise  -- __add is called

print("=== __add ===")
print("Okabe's position: " .. tostring(pos_okabe))
print("Makise's position: " .. tostring(pos_makise))
print("Combined position: " .. tostring(pos_sum))
print("")

-- -----------------------------------------------
-- 6. OOP pattern — class implementation
-- -----------------------------------------------

-- Implement a class and instance pattern with metatables
local LabMember = {}
LabMember.__index = LabMember  -- set the metatable for instances to the class itself

-- Define the constructor (new method)
function LabMember.new(name, lab_no, iq)
    local self = setmetatable({}, LabMember)
    self.name   = name
    self.lab_no = lab_no
    self.iq     = iq
    return self
end

-- Define instance methods
function LabMember:introduce()
    return string.format(
        "My name is %s. Lab Member #%s, IQ %d.",
        self.name, self.lab_no, self.iq
    )
end

function LabMember:is_genius()
    return self.iq >= 160
end

-- Create instances
local member1 = LabMember.new("Rintaro Okabe", "001", 170)
local member2 = LabMember.new("Kurisu Makise", "004", 204)
local member3 = LabMember.new("Itaru Hashida", "003", 110)

print("=== OOP pattern ===")
print(member1:introduce())
print(member2:introduce())
print(member3:introduce())
print("")
print("Is Okabe a genius? " .. tostring(member1:is_genius()))
print("Is Makise a genius? " .. tostring(member2:is_genius()))
print("")

-- -----------------------------------------------
-- 7. getmetatable / rawget verification
-- -----------------------------------------------

print("=== getmetatable / rawget ===")
print("okabe's metatable: " .. tostring(getmetatable(okabe)))
-- rawget bypasses __index, so prototype fields cannot be retrieved
print("rawget organization: " .. tostring(rawget(okabe, "organization")))
print("normal access organization: " .. okabe.organization)
lua steinsgate_metatable.lua
=== __index (table inheritance) ===
Rintaro Okabe belongs to: Future Gadget Laboratory
Kurisu Makise belongs to: Future Gadget Laboratory
Rintaro Okabe is a lab member.
Kurisu Makise is a lab member.

=== __index (function) ===
mayuri: Mayuri Shiina
suzuha: unregistered: suzuha
daru:   unregistered: daru

=== __newindex ===
Change log:
  SET experiment = Time Machine Experiment
  SET result = World Line Divergence: 1.048596
  SET operator = Rintaro Okabe
Saved: Time Machine Experiment

=== __tostring ===
[007] Suzuha Amane (Lab Member No: 008)
[007] Suzuha Amane (Lab Member No: 008)

=== __add ===
Okabe's position: Vec(3, 5)
Makise's position: Vec(1, 2)
Combined position: Vec(4, 7)

=== OOP pattern ===
My name is Rintaro Okabe. Lab Member #001, IQ 170.
My name is Kurisu Makise. Lab Member #004, IQ 204.
My name is Itaru Hashida. Lab Member #003, IQ 110.

Is Okabe a genius? true
Is Makise a genius? true

=== getmetatable / rawget ===
okabe's metatable: table: 0x...
rawget organization: nil
normal access organization: Future Gadget Laboratory

Common Mistakes

setmetatable only accepts tables as its first argument

In standard Lua, only a table can be passed as the first argument to setmetatable(). Strings automatically have a metatable set internally by Lua so that string library methods are accessible. Overwriting this metatable by users is not intended.

-- NG (using setmetatable on a string causes an error)
local s = "hello"
setmetatable(s, { __index = function(t, k) return k end })
-- bad argument #1 to 'setmetatable' (table expected, got string)
-- OK (wrap it in a table)
local s = setmetatable({ value = "hello" }, {
    __tostring = function(self) return self.value end
})
print(tostring(s))   -- "hello"

Missing that __metatable field is the cause when getmetatable returns nil

When a __metatable field is set in a metatable, getmetatable() returns that value instead of the metatable itself. This is a mechanism to protect the metatable from external access.

local mt = { __metatable = "protected" }
local t  = setmetatable({}, mt)
print(getmetatable(t))   -- "protected" (the value of __metatable is returned, not the metatable)

-- attempting to overwrite the metatable causes an error
local ok, err = pcall(function() setmetatable(t, {}) end)
print(ok, err)   -- false, cannot change a protected metatable

Multiple instances sharing a metatable end up sharing fields

Instance fields must always be stored on the instance's own table. Writing directly to the metatable (class table) affects all instances that use that class.

-- NG (storing a field directly on the metatable)
local Animal = {}
Animal.__index = Animal
Animal.name = "unnamed"   -- writing to the class shares the value across all instances

local a1 = setmetatable({}, Animal)
local a2 = setmetatable({}, Animal)
a1.name = "Goku"          -- written to a1's own table (this part is fine)
print(a2.name)             -- "unnamed" (Animal.name is returned via __index)
-- OK (store fields on each instance's own table via a constructor)
function Animal.new(name)
    return setmetatable({ name = name }, Animal)   -- name is held by the instance itself
end

local a1 = Animal.new("Goku")
local a2 = Animal.new("Vegeta")
print(a1.name, a2.name)   -- "Goku"  "Vegeta" (independent)

Overview

A metatable is a mechanism for adding special behavior to a Lua table. Attaching a metatable mt to a table t with setmetatable(t, mt) causes metamethods like __index and __newindex to be called automatically during certain operations. Specifying a table as __index enables prototype-chain inheritance, and specifying a function enables dynamic default value generation. __tostring customizes the display when print() is used, and arithmetic metamethods like __add implement operator overloading. In OOP patterns, the common approach is to set ClassName.__index = ClassName and then create instances with setmetatable({}, ClassName). Use rawget() / rawset() when you want to bypass metatable processing. For the basics of table operations, see Table Basics as well.

If you find any errors or copyright issues, please .