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. The practical consequence is that bracket access can return data you never put in the table, while rawget shows you only what is really there.

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. Without rawget, you cannot tell whether a value was stored directly or inherited through the metatable chain.

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. Each time you access a key normally, Lua checks the metatable’s __index, which might try to look up the same key, triggering __index again in an endless cycle that overflows the call stack.

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

  • rawset — write a table value without invoking __newindex
  • next — iterate raw table keys without triggering metamethods
  • lua-metatables — learn how __index and __newindex work