Ipairs{} and next() usage

Hello Developers,

Recently, I saw this line of code which puzzled me.

local inext = ipairs{}
local t = {"ex1", "ex2", "ex3"}

for _, v in inext, t, 1 do
    print(v)
end

Output: “ex2”, “ex3”
(The number which is in place of 1 determines where to start iteration in the table)

I am confused about what ipairs{} and next() really means. Does it matter what I put in the table ipairs{}? I’ve tried putting extra stuff in the ipairs table, but nothing happened.

Also, what does it mean to have multiple arguments in a for loop such as

--example 1
for i, v in t1, t2, 1 do end

-- example 2
for i, v, k, y in t do end
2 Likes

First of all ipairs is supposed to be used on non dictionary tables such as {“Dog”, “Cat”, “Apple”}, while pairs is for dictionarys such as {[“Dog”] = “Dog”, [“Cat”] = “Cat”}.

local inext = ipairs{}
local t = {"ex1", "ex2", "ex3"}

for _, v in inext, t, 1 do
    print(v)
end

This code snippet you provided is just another fancy way of writing a for loop. I’m assuming this ipairs{} declares that it is in fact a table and t is the actual table you are looping through.

If you want to learn more about what next is used for here is an article that I found that goes over briefly what it means. Next is usually used for dictionarys and not regular tables.

1 Like

What you’ve discovered are ‘iterator functions’. You see, pairs and ipairs are not keywords, but rather global functions, which wrap other functions called ‘iterators’.

When these globals are called, they return a tuple of three values. The first of which being the underlying iterator, and the latter two being its starting arguments. What the for keyword does, is it then interprets this tuple to create a loop, whereby the returned iterator is repeatedly called. On each iteration, all of the values this iterator returns, assume the variables defined on the left-side of the in keyword. The first of these values then becomes the second argument of the subsequent call, whereas the first parameter always remains constant (as its intended use, is to be a static reference to the object being iterated over). This loop then continues, until the first value that the iterator returns, is nil.

The iterator returned by the pairs global, behaves exactly in the manner as we’d expect the next global to. This implies that the iterator returned by ipairs, can be thought of as a sort of inext–a next which only concerns itself only with the array portion of the table. And that’s exactly what’s happening in the code-snippet you’ve provided. They’re calling ipairs to return its underlying iterator function; and then manually supplying the initial tuple fed to for. As you point out, this has the advantage of allowing them to specify an arbitrary first index for iteration, as opposed to beginning from the very start.

You don’t really need to worry about pairs and ipairs anymore nowadays in Luau as stated here in this article (unless you want the loop to stop at the first nil value it finds as the article explains)

You can simply do:

for _, v in t do print(v) end

And it will work correctly without error

1 Like

So is there official lua or Roblox documentation on this? (The parameters after in) You said the first parameter is the function type that will be used, the second one is the table, and the third is the number that the function starts iterating with using next?

The in keyword is only used joined with a for loop in Lua/Luau. The values (e.g. i, v) are simply the values that the iterator function returns (cf. @AskWisp’s response). Technically an iterator can return any number of arguments.

The arguments after in are the iterator function and its initial arguments that are passed to the iterator on the first iteration. When you use an iterator generator like pairs() or ipairs(), these return a new function (the iterator) along with the initial arguments. E.g. if you do print(pairs({})), you will print the iterator function, the table itself, and the starting index (nil).

Typically, you shouldn’t need to worry about any of this stuff. Creating custom iterators can be of some use, but very limited in my experience. In over 15 years, I’ve maybe had to write 5 or so custom iterator functions in Lua.


This hopefully explains why doing for k, v in pairs(t) do is equivalent to for k, v in next, t do. Because, pairs is returning next, along with nil (the starting index, which next interprets as “go to the first item”). Thus, pairs(t) could expand to next, t in terms of a generic for loop.

So the human-readable signature could look something like this:

for key, value in iteratorFunction, ...initialArgsToTheIterator do

Every time the iteratorFunction is called (each iteration of the loop), it is responsible for going to whatever the next value is expected and returning said value (e.g. key, value). If the iterator returns nil, the iteration is considered complete and the for loop exists.

2 Likes

IMO the biggest use case of custom iterators would be to reject the tradition of only having two return values in an iterator.

It works fine for iterating tables, because it gives you everything you need:

for index, value in table do ...

But what if you want to iterate something more complicated that has more? For example, trying to iterate through a 3-dimensional table (a tensor) to get the coordinate and value of each voxel.

With the casual approach, you would have to pack them inside a table first, and then later unpack them inside the iteration.

--the :iter() method here returns a table filled with the serialized voxel data
for _, voxelData in tensor:iter() do
	--unpack the position value
	local xPosition: number = voxelData[1]
	local yPosition: number = voxelData[2]
	local zPosition: number = voxelData[3]
	--unpack the stored value
	local value = voxelData[4]
	
	--do stuff
end

Not only is this tedious, it also wastes performance, because you are creating new tables just to keep the data together, and then later indexing said table.

But if you use coroutines, you can configure the iterator to return just as many values as you wanted:

--the :iter() method here returns a coroutine function
for x, y, z, v in tensor:iter() do
	--look how neat this looks!
end

live demonstration with a crappy module i made

2 Likes

What would a “3 Dimensional Table” look like in code?

So if the arguments after the iterator functions are being passed to that function, is this;

for _, v in inext, t, 1 do
    print(v)
end

equal to

for _, v in ipairs(t, 1) do
    print(v)
end

And you said the pairs function sets the starting number as nil, but I tested it and the second argument being passed affects the output with pairs. What does that second argument do?

type Table3D<T> = {{{T}}}

local t: Table3D<string> = {
	[1] = {
		[1] = {
			[1] = 'hi',
			[2] = 'hi again',
		},
		[2] = {
			[1] = 'peanut',
			[2] = 'butter',			
		}
	},
	[2] = {
		[1] = {
			[1] = 'foo',
			[2] = 'bar',
		},
		[2] = {
			[1] = 'apple',
			[2] = 'banana',			
		}
	},
}

--retrieve position (1, 1, 2)
print(t[1][1][2]) --> 'hi again'

--retrieve position (2, 1, 2)
print(t[2][1][2]) --> 'bar'