Ability to break outside loop

As a Roblox developer, it is currently too hard to break a 2d for loop

Current solution

local Vertices = {}
local FoundVertex

for X = -5, 5 do
	for Y = -5, 5 do
		local Vertex = Vertices[X] and Vertices[X][Y] or nil
		
		if (Vertex) then
			-- break the entire loop
			
			FoundVertex = true
			
			break
		end
	end
	
	if (FoundVertex) then
		break
	end
end

Proposed solution

local Vertices = {}

local OuterLoop = for X = -5, 5 do
	for Y = -5, 5 do
		local Vertex = Vertices[X] and Vertices[X][Y] or nil
		
		if (Vertex) then
			break OuterLoop
		end
	end
end

Passing a variable with it’s value set to a loop will break that loop and any for loops within the loop, making it much easier to break 2d loops

5 Likes

:red_circle: Since a for loop isn’t a function at all it won’t return anything, so they would directly deny it. They already denied adding a new metamethod to detect when a table changes, so imagine this that is more complicated.

You can achieve something like this already though, using coroutines. I will show you a simple example, you can make it better, is just to give you an idea:

local Thread = coroutine.create(function()
   for X = 1, 10 do
	  for Y = 1, 10 do
		  if (Y == 10) then
			  return "Hello World!"
		  end
	   end
    end
end)

local _, Value = coroutine.resume(Thread)
print(Value)
local Thread = coroutine.create(function()
   coroutine.yield("Hello World!") --> Useful to make a code that returns to the main function call from multiple functions call.
end)

local _, Value = coroutine.resume(Thread)
print(Value)

Not a coroutine but can work:

local function Loop()
   for X = 1, 10 do
	  for Y = 1, 10 do
		  if (Y == 10) then
			  return "Hello World!"
		  end
	   end
    end
end

print(Loop())

Output for the three codes:

Hello World!
4 Likes

This isn’t going to happen anytime soon because:

  • It has such a niche use-case with a reasonable workaround

  • It breaks Lua norms for how logic statements work

  • You shouldn’t be using nested tables in your example to begin with. Assuming you’re nesting [Y] in [X] to save memory, Lua is still going to treat the table as a dictionary because you’re manually indexing it, nullifying any performance benefit you would get.

2 Likes

This is a feature many other languages have and it’s usually called “labeled breaks/continues” or some such. It’s already being tracked in the RFC on the Luau repo but it looks like it won’t be added anytime soon. However whether it will be added at all in the far future it’s uncertain.

2 Likes

@MagmaBurnsV @focasds Replying kind of generally and informationally here, forgive me for the long post. (Some stuff is also not particularly relevant to the original feature request)

Lua already vaguely has the capability to for this. Ignoring that though it’s definitely useful and beneficial control flow and I wouldn’t really say that it’s particularly confusing. Just because you can’t break out of a loop like this right now doesn’t at all mean it’s the norm, or, well, if it does it doesn’t say a whole lot for/against the feature request, otherwise every feature request that wants to make something impossible possible should be considered bad because it’s technically against the norm.

In lua 5.2 there are even labeled gotos which are a whole step up from this and would allow you to jump to different places in the code. I believe that this was considered on the luau GitHub but I don’t know if there was a conclusion or what it was.

While technical limitations are definitely important to consider when writing a feature request, it’s ultimately up to Roblox. A feature request for something technologically challenging doesn’t necessarily make a bad feature request I don’t think, so it’s probably good to keep that in mind. These kinds of requests are always more “controversial” because people often get very stuck on specifics (like syntax or implementation details) which I think is not really always warranted for some requests.


@focasds
Your example does show a relatively good way to handle this without labeled breaks I think. The coroutines aren’t necessary for the use case given by the OP, but your last snippet demonstrates that by having an outer function, you can ofc return to break out of all loops in that function, and that’s a clean way to handle this imo, though, I do support the feature request, labeled breaks would definitely be really nice (and really useful for some projects I follow).


Summary

Well, this exact thing already exists in the form of __newindex in entirety, it just has the gimick that if you set the value in the table the metamethod is for it won’t fire anymore because __newindex fires for new values. So, if you’d like to detect changes to a table you can create a blank table with a metamethod that updates the value of some target table you want to detect changes to. Then you can use that “proxy” table instead of the target one.


FYI though there are only a few cases where I can see this being a good idea. In any case I can envision it’s a lot cleaner to create a function/method to do the reading/writing and fire changed events and all that. A metamethod would only really make the syntax more pleasing but the cost is more overhead and ambiguity, and there aren’t many benefits.

I only use __newindex like this when I’m dealing with arbitrary or unknown code that isn’t written by a user of my code and isn’t written by me, e.g. I’ve written a few code sandboxing tools which use __newindex in exactly the way I describe to overwrite what a piece of sandboxed code can do.

If you’re writing code for yourself, or providing an API for an end user, ultimately it makes more sense to do your own read/write API to keep track of changes and detecting table changes in a generalized way would not end up providing a whole lot of benefit.

You can visit this post to know what the issues were for me to request a __changed metamethod to be added: (Remember that currently for every table you want to detect changes you have to create two tables all of the time, the index and the proxy, not counting the metatable since it should be the same)
Better Way to Detect Changes In Tables


On topic:
For the coroutine example I gave is useful for you if you want to return from a certain function all the way back, that’s why I added that there.

Code: (This is a simple version and having other kinds of yield will not make it work as expected, you can fix that in other ways though)

local function C()
      coroutine.yield("Hello World!")
end

local function B()
      C()
end

local function A()
      B()
end

local _, Value = coroutine.resume(coroutine.create(A))
print(Value)

Output:

Hello World!

Though I understand is not related to his issue that much because he is talking about loops.


The reason why my mindset has changed about having “complex feature requests” is because of how the Dev-Forum members have replied to me through the 2 years and 4 month that makes me think it isn’t gonna be accepted.

A lot of people used to tell me even in discord servers the behavior of goto in Lua is just bad because it can have weird behaviors if you don’t use it correctly. You know, the typical response of people who doesn’t know what they are even talking about and just deny a feature because it can be “complicated for new devs”.


(I understand that replies must be concise and directly to the point, but this shouldn’t be the case all of the time since we are not BOTS. Thank you.) (Cuz I am tired of people private messaging me that I am off topic when in reality a lot of people is giving their opinion on a feature)

goto being bad is a nuanced but still relatively agreed upon thing. The behavior that goto introduces is easily misused and very rarely useful to the average programmer, as most things can already be expressed with while/if/for/repeat. The difference between this feature request and goto is that it heavily limits the complexity to just loops, which are already structured.

In better terms, labeled loops allow us to express useful behavior we already could but with simpler syntax and less overhead. goto, on the other hand, allows us to express entirely different behavior we couldn’t before without a full state machine, which is questionably useful.