luaguides

rawget

rawget(table, index)

rawget retrieves a value from a table while bypassing any metatable __index metamethod. When you need to inspect what is actually stored in a table, separate from what a metatable might provide as a fallback, this is the function to reach for.

Parameters

ParameterTypeRequiredDescription
tabletableYesThe table to read from
indexanyNoThe key to look up

rawget returns the value stored at table[index], or nil if the key does not exist in the table’s raw storage. It raises an error if the first argument is not a table.

How It Differs From Bracket Access

When you access a table key with bracket notation, Lua first looks in the table’s own storage. If the key is not found, and the table has a metatable with an __index metamethod, Lua calls that metamethod to provide a value. rawget skips this step entirely.

local t = setmetatable({real_key = "exists"}, {
    __index = function()
        return "default_from_metatable"
    end
})

print(t.real_key)            -- "exists" (found in table)
print(rawget(t, "real_key")) -- "exists" (same result when key exists)

print(t.missing_key)         -- "default_from_metatable" (triggered by __index)
print(rawget(t, "missing_key")) -- nil (key not in table, __index ignored)

Detecting Actual Table Membership

A practical use for rawget is checking whether a key truly exists in a table, rather than being provided by a metatable fallback. This matters when working with configuration systems, default value tables, or prototype-based inheritance.

local defaults = {width = 800, height = 600, fullscreen = false}
local user_settings = setmetatable({}, {
    __index = defaults
})

-- Bracket access finds defaults via __index
print(user_settings.width)        -- 800

-- rawget sees only what is actually stored in user_settings
print(rawget(user_settings, "width")) -- nil (not stored directly)

-- A key explicitly set in user_settings hides the default
user_settings.width = 1280
print(rawget(user_settings, "width")) -- 1280

Avoiding Infinite Recursion in __index

When you implement a custom __index that wraps access to an underlying table, using rawget to read from that inner table prevents __index from firing again and causing infinite recursion.

local wrapper = setmetatable({}, {
    __index = function(self, key)
        local inner = rawget(self, "_inner")
        if inner then
            return rawget(inner, key)
        end
    end
})

local inner = {data = "stored"}
rawset(wrapper, "_inner", inner)

print(wrapper.data) -- "stored"

Without rawget, the __index function would try to look up "_inner" again, find nothing in wrapper’s own storage, call __index again, and recurse forever.

Checking Key Existence Reliably

If you need to know whether a key exists directly in a table, rawget is the correct approach. Regular bracket access cannot distinguish between a key set to nil and a missing key, since both produce nil as the result. And bracket access on a missing key would trigger __index on a metatable.

local function has_own_key(tbl, key)
    return rawget(tbl, key) ~= nil
end

local t = setmetatable({exists = true}, {
    __index = function() return "fallback" end
})

print(has_own_key(t, "exists"))  -- true (key really exists)
print(has_own_key(t, "missing")) -- false (key doesn't exist directly)
print(t.missing)                 -- "fallback" (but it came from __index, not the table)

Gotchas

You cannot distinguish a missing key from one stored as nil. Both return nil from rawget. If you need to detect explicit nil assignments, you need a different approach such as storing a sentinel value.

rawget only bypasses __index for reads. Writing to a table still triggers __newindex if defined. For raw writes, use rawset.

Passing a non-table throws an error. Unlike some Lua functions that coerce arguments, rawget requires a table as its first argument.

See Also

  • next — iterate raw table keys without triggering metamethods
  • lua-metatables — learn how __index and __newindex work