Unpack()ing multiple tables into a bigger table has unexpected behavior

Reproduction Steps
In Roblox Studio run the following command in the command line:

print({
    unpack({"a", "b", "c", "d", "e", "f", "g", "h", "i"}),
    unpack({"easy", "as", "one", "two", "three"})
})

Expected Behavior
I would expect the following in the output:

▼  {
                    [1] = "a",
                    [2] = "b",
                    [3] = "c",
                    [4] = "d",
                    [5] = "e",
                    [6] = "f",
                    [7] = "g",
                    [8] = "h",
                    [9] = "i",
                    [10] = "easy",
                    [11] = "as",
                    [13] = "one",
                    [14] = "two",
                    [15] = "three"
                 }  -  Edit

Every item in the first unpacked list is added to the new list in order and every item in the second unpacked list is added sequentially where the first list left off.

Actual Behavior
What I get instead:

▼  {
                    [1] = "a",
                    [2] = "easy",
                    [3] = "as",
                    [4] = "one",
                    [5] = "two",
                    [6] = "three"
                 }  -  Edit

Only the first item of the first list was added to the new list but every item of the second list was added sequentially after that.

Workaround
This is the best way I’ve found of combining two or more lists. It’s kind of messy and I wish I didn’t need to define a function for this.

function CombineLists(... : table)
    local Output = {}
    local i = 1
    for x, list in ipairs({...}) do
        for y, item in ipairs(list) do
            table.insert(Output, i, item)
            i = i + 1
        end
    end
    return Output
end

Issue Area: Engine
Issue Type: Other
Impact: Low
Frequency: Rarely

1 Like

This is intended behavior.

table.unpack returns multiple values. The defined behavior in that case is that if it is not the last one in a list of expressions then all but the first returned value will be discarded.

From the book:

Lua always adjusts the number of results from a function to the circumstances of the call. When we call a function as a statement, Lua discards all of its results. When we use a call as an expression, Lua keeps only the first result. We get all results only when the call is the last (or the only) expression in a list of expressions.


6 Likes

Adding to the post above, in Layman’s terms:

Combining tuples into a new tuple:
tuple, tuple, tuple

Is supposed to result in actually passing:
{tuple}[1], {tuple}[1], tuple

Only the last tuple will be completely combined into the new tuple.

It’s probably useful in combining functional returns where the first returned argument is much more important.

I see now how changing this behavior could impact how code is written and could create some headaches.
For instance, if you had two functions that return values and you want to add their returned values to a table it would be strange if the second function’s value was placed at index 3 because the first table returned more than one value.

function funcA()
    return "a", "b"
end
function funcB()
    return "c"
end
print( {funcA(), funcB()} )

==>> {"a", "b", "c"} <<== -- "c" isn't at index 2 like would be expected

or when assigning two variables inline and having one function fill both of them and leaving the second’s returned value to be discarded.

local x, y = funcA(), funcB()
print(x, y)
-- x = "a", y = "b" instead of "c" like the programmer would likely be expecting.

So I can see why this is intended behavior. Thank you for your reply.