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
| Parameter | Type | Required | Description |
|---|---|---|---|
table | table | Yes | The table to read from |
index | any | No | The 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
__indexand__newindexwork