table.move
table.move(a1, f, e, t [,a2]) Signature and semantics
table.move copies e - f + 1 consecutive elements from the source table a1 into a destination table a2, starting at index t. The function returns a2, which makes the call chainable. In its most common form, the move is “in place” because the optional a2 defaults to a1.
table.move(a1, f, e, t [,a2]) --> a2
Here is the form the Lua reference manual gives for table.move: the multiple assignment below. Read it that way and the function performs a single block copy, not a per-element loop. The move happens atomically, so callers never observe a half-finished state even when the source and destination ranges overlap.
a2[t], a2[t+1], ..., a2[t+(e-f)] = a1[f], a1[f+1], ..., a1[e]
That single line is the whole spec: read e - f + 1 elements from a1[f..e] and write them in order into a2[t..t+(e-f)]. If e < f, no elements are moved.
Parameters
| Param | Type | Description |
|---|---|---|
a1 | table | Source table. Elements are read from a1[f..e]. |
f | integer | Source start index (1-based). Must be at least 1. |
e | integer | Source end index, inclusive. Must be at least f. |
t | integer | Destination start index in a2. Any integer is allowed; a2 grows automatically. |
a2 | table? | Destination table. Defaults to a1 for an in-place move. |
The destination range can overlap with the source range. The C code picks the correct direction (forward or backward) so the move behaves like memmove, not memcpy. This is why table.move exists: a hand-written for i=f,e do a2[t+i-f] = a1[i] end only gets the t < f direction right and silently clobbers source values when t > f.
Basic copy between two tables
The shortest useful call is a sequence-to-sequence copy:
local src = {10, 20, 30, 40, 50}
local dst = {}
table.move(src, 1, 5, 1, dst)
-- dst == {10, 20, 30, 40, 50}
-- table.move returns dst, so you can write:
-- local copy = table.move(src, 1, #src, 1, {})
Because the function returns a2, a one-liner shallow copy of a sequence is table.move(src, 1, #src, 1, {}). The destination does not need to be sized in advance: Lua tables grow as t increases past the current length.
In-place shift with overlapping ranges
When you omit a2, the source and destination are the same table. The function still produces the correct result when the ranges overlap, which a manual loop cannot do:
local t = {1, 2, 3, 4, 5}
table.move(t, 1, 2, #t) -- move t[1..2] to t[4..5]
-- t == {1, 2, 3, 1, 2}
Here t[1..2] is the source range and t[4..5] is the destination range. The values 1 and 2 are read into temporary slots first, so neither is overwritten before it is consumed. Trying the same shift with for i=1,2 do t[3+i] = t[i] end corrupts the result because the second iteration reads the already-overwritten t[2].
Slicing a sub-range
Passing an explicit (f, e) pair is a clean way to extract a sub-array without computing #:
local buf = {"a", "b", "c", "d", "e"}
local window = {}
table.move(buf, 2, 4, 1, window)
-- window == {"b", "c", "d"}
This works equally well as the consumer side of a ring buffer: keep the live data in buf and copy the current window into a fresh window table on every tick.
Erasing a range with nil
Because reading a missing key yields nil, moving from a table that has nothing at the source indices clears the destination slots:
local t = {1, 2, 3, 4, 5}
local nils = {} -- #nils == 0
table.move(nils, 1, 3, 2, t)
-- t[2], t[3], t[4] are now nil
-- t == {1, nil, nil, nil, 5}
This is the cleanest stdlib-only way to nil out a contiguous range of a sequence.
Why not a for loop?
You can rewrite table.move as a for loop, and the result is identical for non-overlapping ranges. The difference shows up when the destination range sits inside or after the source range:
local t = {1, 2, 3, 4, 5}
table.move(t, 1, 3, 3) -- move t[1..3] to t[3..5]
-- t == {1, 2, 1, 2, 3}
A naive forward loop breaks here. By the time the third pass reads from t[3], that slot already holds the 1 written on the first pass, so the original 3 is gone:
for i = 1, 3 do
t[2 + i] = t[i]
end
-- t == {1, 2, 1, 2, 1} -- the i=3 step reads t[3] after it was clobbered
table.move walks the ranges in the correct direction and gets the right answer. The C code picks forward or backward by comparing t and f, so you do not have to think about it.
A slice-and-join pattern
Once you have sliced a window out of a buffer, the next step is usually to do something with it. The natural follow-up is table.concat, which flattens a sequence of strings into one. The two together build a tiny read-only view without any helper library:
local buf = {"line 1\n", "line 2\n", "line 3\n", "line 4\n"}
local frame = {}
table.move(buf, 2, 3, 1, frame)
print(table.concat(frame))
-- line 2
-- line 3
This is the kind of pipeline where table.move pays for itself: the slice is a single C call, not a per-element loop, and the source buffer stays untouched for the next frame.
Gotchas
- Lua 5.3 or later.
table.movewas introduced in Lua 5.3. It is not in Lua 5.1 or 5.2, and it is not in stock LuaJIT 2.0/2.1. Thelua-compat-5.3rock provides a polyfill for older interpreters. - Indices must be integers. Floats with integer values (
2.0) work because Lua converts them. Non-integer floats andNaNraise an error. - No metamethods. The function treats indices positionally and does not invoke
__indexor__newindex. If a table has either metamethod,table.moveskips them and reads or writes the raw slot. #is not consulted. You passeexplicitly. For a sparse sequence, pass anepast the holes; using#tstops at the first hole.- Return value is the destination table, not a count.
local n = table.move(t, 1, 3, 1)gives youtback, not the integer 3. - Forward and backward overlap both work. The C code picks direction by comparing
tandf. Prefertable.moveover aforloop whenever the ranges might overlap.
See also
table.insert— element-level append or insert; pair withtable.movefor bulk shifts.table.remove— element-level delete; pair withtable.movefor splice operations.table.concat— flatten a sequence of strings into one string; useful after slicing withtable.move.table.sort— in-place ordering, the same default-a2modetable.moveuses.unpack— the inverse direction: spread a table into multiple return values.- Tables introduction — sequence basics that every
table.movecall assumes. - Arrays and lists — array semantics, copying, and slicing patterns.
- Metatables guide — why
table.movedoes not invoke__indexor__newindex.