table.unpack
table.unpack(list [, i [, j]]) table.unpack takes a contiguous slice of a sequence-style table and produces a stream of multiple values. Lua then places those values wherever a multi-value is allowed: a multi-assignment, the argument list of a function call, or a return statement. It is the table-side equivalent of spreading an array, and the inverse of table.pack.
The signature is short and worth memorizing:
table.unpack(list [, i [, j]])
The standalone lua interpreter also exposes a global unpack as an alias for table.unpack, kept around for backward compatibility with Lua 5.1 code. In anything you write today, prefer table.unpack. The global is not guaranteed to exist in every embedded host.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
list | table | required | Source sequence. Indexed with integer keys. May be a userdata with the right metamethods (see Gotchas). |
i | integer | 1 | First index to unpack. Can be negative. If i > j, the call produces zero values. |
j | integer | #list | Last index to unpack, inclusive. Default uses the raw # operator on list. |
The defaults come straight from the C source: i defaults to 1, and j defaults to the result of luaL_len(L, 1), which is #list. Passing j explicitly bypasses the length operator, which matters when the table is not a clean sequence.
Return Value
Returns list[i], list[i+1], …, list[j] as a single multi-value. A few concrete cases:
i == jreturns exactly one value,list[i].i > jreturns zero values, not a singlenil. In a multi-assignment the corresponding target is left unset, which is a real difference fromnilif you later use_ENVstrict-mode checks.i < jbut the range is otherwise normal returns the full slice as separate values.
select('#', ...) is the standard way to count the values table.unpack returns, and the difference between zero values and one nil shows up clearly there:
local t = {10, 20, 30}
print(select('#', table.unpack(t, 2, 2))) -- 1: one value
print(select('#', table.unpack(t, 5, 4))) -- 0: zero values, not one nil
local a = table.unpack(t, 5, 4)
print(a == nil) -- true (a is unset)
It can raise "too many results to unpack" if the explicit range is so large that the C stack cannot hold the return values. The C function guards against this with an INT_MAX check before pushing anything, so the error surfaces as a normal Lua error rather than a crash.
Worked Examples
The examples below all run in Lua 5.4. Each code block shows the printed output on the line below it.
Spread into a multi-assignment
This is the simplest use: a list of three values flows into three local variables in a single multi-assignment. The unpacked slice has to match the number of targets, and any extra values are dropped while any missing targets are filled with nil.
local t = {10, 20, 30}
local a, b, c = table.unpack(t)
print(a, b, c)
-- 10 20 30
Forward a stored argument list to a function
You can hand a stored list of values to any function that takes them positionally, because Lua’s multi-value semantics let table.unpack step into the call’s argument list at the right position. This is the most common real-world use of table.unpack: arguments arrive in a table (loaded from JSON, collected dynamically, or built up across several branches) and you want to forward them on without unpacking them yourself.
local function add(x, y, z) return x + y + z end
local args = {1, 2, 3}
print(add(table.unpack(args)))
-- 6
Custom i and j
The i and j parameters let you unpack any contiguous slice of a sequence, including a deliberately empty one. Passing i alone uses the table’s length as j. Passing j = 0 is the idiomatic way to ask for an empty result that contributes zero values to the surrounding expression.
local t = {"a", "b", "c", "d", "e"}
print(table.unpack(t, 2)) -- i=2, j defaults to #t = 5
-- b c d e
print(table.unpack(t, 2, 4)) -- i=2, j=4
-- b c d
print(table.unpack(t, 1, 0)) -- empty range, zero values
-- (no output)
Round-trip with table.pack
table.pack records the actual count of stored values in a field called n. That field is the canonical companion to table.unpack: read it back as j to preserve the count across a re-pass, even if a stored value happens to be nil (rare with table.pack, but real whenever you build a sequence yourself and need to round-trip the exact count).
local function pair(a, b) return a, b end
local packed = table.pack(pair(1, 2)) -- {1, 2, n=2}
print(packed.n) -- 2
local x, y = table.unpack(packed, 1, packed.n)
print(x, y) -- 1 2
Gotchas
- Holes break the default
j. The default forjis#list, and the Lua manual is explicit that the length operator is defined only for sequences. For{1, 2, nil, 4}the result of#tis implementation-defined (either2or4in PUC Lua), sotable.unpack(t)on a non-sequence is not portable. Passjexplicitly if you cannot guarantee a clean sequence. - Empty range is zero values, not one
nil.local a = table.unpack(t, 1, 0)leavesaunset.select('#', ...)is the tool for “how many values did I get”. - Not raw.
table.unpackuseslua_geti, which respects the__indexmetamethod. A userdata with a metatable that supplies__index(and__lenfor the defaultj) is unpackable. If you need to bypass metamethods, write aforloop withrawget. j = 0is legal and returns zero values. It is a useful sentinel when you are computing a range and want to short-circuit cleanly.- The global
unpackis not portable. It is still defined in the standalone interpreter, but embedded hosts (game engines, OpenResty, Redis scripting) are not required to expose it. Always writetable.unpackin new code.
Two of the gotchas above translate more clearly into code than into prose:
-- Holes: #list is undefined for non-sequences, so pass j explicitly
local t = {1, 2, nil, 4}
print(table.unpack(t, 1, 4)) -- 1 2 nil 4
-- __index metamethod: lua_geti triggers it, so computed or
-- metatable-backed indices are reachable through table.unpack
local mt = { __index = function(_, k) return "v" .. k end }
local u = setmetatable({}, mt)
print(table.unpack(u, 1, 2)) -- v1 v2
Version Notes
| Version | Where it lives | Notes |
|---|---|---|
| Lua 5.0 / 5.1 | global unpack in the basic library | Same (t [, i [, j]]) shape. |
| Lua 5.2 | moved to table.unpack | Standalone interpreter keeps a global alias. |
| Lua 5.3 | table.unpack | No behavior change. |
| Lua 5.4 | table.unpack (current) | Same as 5.3. Adds the INT_MAX guard against stack overflow on pathological explicit ranges. |
| LuaJIT | both unpack and table.unpack | Either form works. |
See Also
select:select('#', ...)returns the count of varargs;select(n, ...)returns values from indexnonward. Pair withtable.unpackwhen you also need the count.table.insert: the natural inverse for building sequences.ipairs: iterate a sequence by index. Slower than unpacking if you need every value.pairs: generic iteration. Works for non-sequence tables thattable.unpackcannot handle.next: the primitive bothpairsandipairsbuild on.print: used in every example above.- Functions in Lua: varargs and multiple-return-value context.
- Tables in Lua: table-as-sequence primer.
- Arrays and Lists: array semantics and the sequence rule.