luaguides

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

NameTypeDefaultDescription
listtablerequiredSource sequence. Indexed with integer keys. May be a userdata with the right metamethods (see Gotchas).
iinteger1First index to unpack. Can be negative. If i > j, the call produces zero values.
jinteger#listLast 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 == j returns exactly one value, list[i].
  • i > j returns zero values, not a single nil. In a multi-assignment the corresponding target is left unset, which is a real difference from nil if you later use _ENV strict-mode checks.
  • i < j but 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

  1. Holes break the default j. The default for j is #list, and the Lua manual is explicit that the length operator is defined only for sequences. For {1, 2, nil, 4} the result of #t is implementation-defined (either 2 or 4 in PUC Lua), so table.unpack(t) on a non-sequence is not portable. Pass j explicitly if you cannot guarantee a clean sequence.
  2. Empty range is zero values, not one nil. local a = table.unpack(t, 1, 0) leaves a unset. select('#', ...) is the tool for “how many values did I get”.
  3. Not raw. table.unpack uses lua_geti, which respects the __index metamethod. A userdata with a metatable that supplies __index (and __len for the default j) is unpackable. If you need to bypass metamethods, write a for loop with rawget.
  4. j = 0 is legal and returns zero values. It is a useful sentinel when you are computing a range and want to short-circuit cleanly.
  5. The global unpack is 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 write table.unpack in 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

VersionWhere it livesNotes
Lua 5.0 / 5.1global unpack in the basic librarySame (t [, i [, j]]) shape.
Lua 5.2moved to table.unpackStandalone interpreter keeps a global alias.
Lua 5.3table.unpackNo behavior change.
Lua 5.4table.unpack (current)Same as 5.3. Adds the INT_MAX guard against stack overflow on pathological explicit ranges.
LuaJITboth unpack and table.unpackEither form works.

See Also

  • select: select('#', ...) returns the count of varargs; select(n, ...) returns values from index n onward. Pair with table.unpack when 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 that table.unpack cannot handle.
  • next: the primitive both pairs and ipairs build 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.