196

I have been dealing a lot with Lua in the past few months, and I really like most of the features but I'm still missing something among those:

  • Why is there no continue?
  • What workarounds are there for it?
finnw
  • 47,861
  • 24
  • 143
  • 221
Dant
  • 1,961
  • 2
  • 12
  • 3
  • 17
    Since this question was asked, Lua got a `goto` statement which can be used to implement continue. See the answers below. – lhf Dec 11 '12 at 17:25

11 Answers11

131

In Lua 5.2 and upwards the best workaround is to use goto:

-- prints odd numbers in [|1,10|]
for i=1,10 do
  if i % 2 == 0 then goto continue end
  print(i)
  ::continue::
end

This is supported in LuaJIT since version 2.0.1

DarkWiiPlayer
  • 6,871
  • 3
  • 23
  • 38
catwell
  • 6,770
  • 1
  • 23
  • 21
  • 77
    I hope they include an actual `continue` one day. The `goto` replacement doesn't look very nice and needs more lines. Also, wouldn't that create trouble if you had more than one loop doing this in one function, both with `::continue::`? Making up a name per loop doesn't sound like a decent thing to do. – E. T. Jan 08 '14 at 23:33
  • 4
    The `goto` gives expressiveness to the language, since `continue` continues to where? Instead `continue 2` or `continue 3` when nesting loops, a named `goto` makes clearer, and even powerful giving the choice to the coder. Maybe the price paid is to write one more line of code, but still it implements a really "one way to go" instead of some languages that implements a thing now and as it is not so abrangent needs to implement N ways to do the same thing in obscure ways. – Arkt8 Sep 02 '21 at 17:10
  • 8
    That argument can be extended to `break` and even `return`, `break` to where? and `return` to where? Also, the `break` functionality can be achieved with a `goto` as well just fine, creating more than "one way to go". But Lua has those two keywords and are quite useful. – sharat87 Jan 12 '22 at 03:04
  • 1
    @Arkt8 Usually, languages solve the problem that `continue`/`break` within nested loops can be ambiguous with named statements (e.g. [Java](https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.7) or [Swift](https://docs.swift.org/swift-book/LanguageGuide/ControlFlow.html#ID141)). These are more powerful than plain `continue` and `break` but do not break assumptions about control transfer between basic blocks that are necessary for type inference and/or guaranteed initialization (which Java's and Swift's compiler rely on as well as static analyzers/type checkers for Lua). – Feuermurmel Jan 02 '23 at 19:07
  • @sharat87 I still must admit that a named break is better than "break 1" or "break 2". The "return" is nonsense, as every function just can return to the outer scope... otherwise it would be an long jump. – Arkt8 Mar 02 '23 at 15:01
  • @Feuermurmel so with goto also... if static analyzers and type checker can follow a continue/break it should be able to follow goto also. It can bring more work to static analyzers and type checkers, but not sound good to put limits on how specific the programmer can be on code just to make the tooling easier to develop. – Arkt8 Mar 02 '23 at 15:06
  • 2
    @Arkt8 There is one difference: A `break` and `continue` will only ever _leave_ one or more lexical scopes, but never _enter_ them. That may or may not make your live harder as a compiler/static analyzer. – Feuermurmel Mar 03 '23 at 13:39
79

The way that the language manages lexical scope creates issues with including both goto and continue. For example,

local a=0
repeat 
    if f() then
        a=1 --change outer a
    end
    local a=f() -- inner a
until a==0 -- test inner a

The declaration of local a inside the loop body masks the outer variable named a, and the scope of that local extends across the condition of the until statement so the condition is testing the innermost a.

If continue existed, it would have to be restricted semantically to be only valid after all of the variables used in the condition have come into scope. This is a difficult condition to document to the user and enforce in the compiler. Various proposals around this issue have been discussed, including the simple answer of disallowing continue with the repeat ... until style of loop. So far, none have had a sufficiently compelling use case to get them included in the language.

The work around is generally to invert the condition that would cause a continue to be executed, and collect the rest of the loop body under that condition. So, the following loop

-- not valid Lua 5.1 (or 5.2)
for k,v in pairs(t) do
  if isstring(k) then continue end
  -- do something to t[k] when k is not a string
end

could be written

-- valid Lua 5.1 (or 5.2)
for k,v in pairs(t) do
  if not isstring(k) then 
    -- do something to t[k] when k is not a string
  end
end

It is clear enough, and usually not a burden unless you have a series of elaborate culls that control the loop operation.

RBerteig
  • 41,948
  • 7
  • 88
  • 128
  • 6
    Coming from a python background this is a confusing answer because every scope there already knows what are its local variables before running. I.e. I expected an unbound local variable error in the case of reaching `until...`. – ubershmekel Nov 01 '12 at 10:55
  • 1
    I know next to nothing about Python. Lua takes a very narrow view of scope for local variables: it is controlled lexically. The scope is limited at the end of the block containing the variable. The quirk is that the condition of the `unless` clause is *inside* the block lexically, because it makes the usual cases clearest. The example above with a local variable `a` shadowing a global variable `a` shows a case where that invisible lexical boundary can be confusing. Lua 5.2 introduces `goto`, but requires that it not be used in a way that would break lexical scoping. – RBerteig Nov 01 '12 at 18:00
  • I think you make a fair point, but when you state "If continue existed, it would have to be restricted semantically to be only valid after all of the variables used in the condition have come into scope". Could you explain why this must be enforced? I don't see why a lookup on "inner a" simply could not result in null as any other "non-defined" symbol in the current scope (which is what I expected). – udoprog Nov 27 '12 at 04:01
  • 3
    There was a lot of discussion of this in the Lua community before the introduction of `goto` into Lua 5.2. Naturally, `goto` has the same issue. They eventually decided that whatever the runtime and/or code generation costs were to protect against it were worth the benefits of having a flexible `goto` that can be used to emulate both `continue` and multi-level `break`. You'd have to search the [Lua list archives](http://lua-users.org/lists/lua-l/) for the relevant threads to get the details. Since they did introduce `goto`, it obviously was not insurmountable. – RBerteig Nov 28 '12 at 20:22
  • 115
    There's nothing "clear enough" about writing code without continue. It's a novice mistake to nest code inside a conditional where a continue should have been used, and the need to write ugly code like that shouldn't receive any sympathy. There's absolutely no excuse. – Glenn Maynard Sep 12 '15 at 23:56
  • 3
    (Least of all by "no compelling use cases". If they don't understand the importance of continue, they have no place designing a language.) – Glenn Maynard Sep 12 '15 at 23:59
  • 5
    This explanation makes no sense. `local` is compiler-only directive - it doesn't matter what runtime insructions are between `local` and variable usage - you don't need to change anything in compiler to maintain same scoping behavior. Yes, this might be not so obvious and need some additional documentation, but, to reiterate again, it requires ZERO changes in compiler. `repeat do break end until true` example in my answer already generates **exactly** the same bytecode that compiler would with continue, the only difference is that with `continue` you wouldn't need ugly extra syntax to use it. – Oleg V. Volkov Feb 12 '16 at 17:27
  • 13
    That you can test the inner variable speaks about flawed design. The condition is outside the inner scope and it should not have access to the variables within it. Consider the equivalent in C: `do{int i=0;}while (i == 0);` fails, or in C++: `do int i=0;while (i==0);` also fails ("was not declared in this scope"). Too late to change that now in Lua, unfortunately. – Pedro Gimeno May 21 '16 at 10:10
  • 2
    while this is true by itself, this is historically incorrect. 'continue' was not added because it would conflict with an existing 'goto' implementation. On the contrary, the wish for continue was continuously uttered to the Lua dev team, until they decided they add goto, because reasons, and thus shut up the 'continue' requests... – axkibe Jun 12 '18 at 13:53
  • 2
    @axkibe Methinks the Lua designers/devs never heard of Dijkstra or even just his (in?)famous "GOTO considered harmful". ;-) – Jürgen A. Erhard Aug 01 '21 at 03:53
  • I've seen the culling-via-`continue` pattern pop up often in my Python code, where a function receives a long list and operates on a small subset chosen using a set of criteria expressed in guard clauses. Now that I'm learning to script a particular program that uses Lua as its scripting language, I need a workaround that doesn't encourage an arrow anti-pattern. – Damian Yerrick Aug 01 '22 at 13:21
  • This particular issue may actually encourage better design by substituting `return` where `continue` would be used. The loop's body becomes a call to function: `for k,v in pairs(t) do; process(k,v); end` , followed by `function process(k,v); if isstring(k) then return end; /*do other stuff*/ end` – user26785 Feb 07 '23 at 17:18
70

You can wrap loop body in additional repeat until true and then use break inside for effect of continue. Naturally, you'll need to set up additional flags if you also intend to really break out of loop as well.

This will loop 5 times, printing 1, 2, and 3 each time.

for idx = 1, 5 do
    repeat
        print(1)
        print(2)
        print(3)
        do break end -- goes to next iteration of for
        print(4)
        print(5)
    until true
end

The break is wrapped in do and end to keep Lua from complaining about the unreachable code below it. This isn't necessary if the break is wrapped in an if condition, as will be the case in most real-world code.

This construction even translates to literal one opcode JMP in Lua bytecode!

$ luac -l continue.lua 

main <continue.lua:0,0> (22 instructions, 88 bytes at 0x23c9530)
0+ params, 6 slots, 0 upvalues, 4 locals, 6 constants, 0 functions
    1   [1] LOADK       0 -1    ; 1
    2   [1] LOADK       1 -2    ; 3
    3   [1] LOADK       2 -1    ; 1
    4   [1] FORPREP     0 16    ; to 21
    5   [3] GETGLOBAL   4 -3    ; print
    6   [3] LOADK       5 -1    ; 1
    7   [3] CALL        4 2 1
    8   [4] GETGLOBAL   4 -3    ; print
    9   [4] LOADK       5 -4    ; 2
    10  [4] CALL        4 2 1
    11  [5] GETGLOBAL   4 -3    ; print
    12  [5] LOADK       5 -2    ; 3
    13  [5] CALL        4 2 1
    14  [6] JMP         6   ; to 21 -- Here it is! If you remove do break end from code, result will only differ by this single line.
    15  [7] GETGLOBAL   4 -3    ; print
    16  [7] LOADK       5 -5    ; 4
    17  [7] CALL        4 2 1
    18  [8] GETGLOBAL   4 -3    ; print
    19  [8] LOADK       5 -6    ; 5
    20  [8] CALL        4 2 1
    21  [1] FORLOOP     0 -17   ; to 5
    22  [10]    RETURN      0 1
DarkWiiPlayer
  • 6,871
  • 3
  • 23
  • 38
Oleg V. Volkov
  • 21,719
  • 4
  • 44
  • 68
  • 5
    This answer is nice, but still requires 3 lines instead of just one. (if "continue" was properly supported) It's a bit prettier and safer than a goto label though, since for that name clashes might need to be avoided for nested loops. – E. T. Aug 21 '14 at 02:28
  • 9
    it does, however, avoid the "real" problem with goto in that you don't have to invent a new identifier/label for each psuedo-continue and that it is less error prone as code is modified over time. *i agree that continue would be useful*, but this IMO is the next best thing (and it really requires two lines for the repeat/until vs. a more formal "continue;".. and even then, if you were that concerned with line counts you could always write "do repeat" and "until true end", for example: https://gist.github.com/wilson0x4d/f8410719033d1e0ef771) – Shaun Wilson Jun 03 '15 at 11:10
  • 2
    Nice to see people actually consider performance and even provide `luac` output on SO! Have a well deserved upvote :) – DarkWiiPlayer Jan 20 '20 at 15:02
22

Straight from the designer of Lua himself:

Our main concern with "continue" is that there are several other control structures that (in our view) are more or less as important as "continue" and may even replace it. (E.g., break with labels [as in Java] or even a more generic goto.) "continue" does not seem more special than other control-structure mechanisms, except that it is present in more languages. (Perl actually has two "continue" statements, "next" and "redo". Both are useful.)

Stuart P. Bentley
  • 10,195
  • 10
  • 55
  • 84
  • 32
    I love the admittance: "Both are useful" right after an explanation of "we're not going to do it" – David Ljung Madison Stellar Jan 11 '18 at 22:15
  • 3
    It was to note the scope that they *were* looking to address when they *did* do it, by adding a "goto" construct in 5.2 (which hadn't been released when this answer was written). See [this answer from 2012](https://stackoverflow.com/a/12929685/34799), after 5.2.0 was released. – Stuart P. Bentley Jan 14 '18 at 05:31
  • 20
    Right - because 'goto' is well-recognized to be a decent programming construct. (end sarcasm) Ah well. – David Ljung Madison Stellar Jan 15 '18 at 07:03
  • 7
    But it did not sound more reasonable than "I just forgot to put `continue` into Lua, sorry." – neoedmund Apr 10 '18 at 07:46
  • Is there an alive link for this? – Zane Dufour Jul 28 '22 at 16:03
  • 1
    The Ada language, used for mission-critical applications, also does not have `continue` nor `break`. See this [https://stackoverflow.com/questions/28787843/starting-the-next-iteration-of-a-loop-immediately-in-ada]stackoverflow discussion. – chikega Oct 16 '22 at 18:53
  • To me it was a poor design choice, similar to not including a split function for strings. My eyes hurt when I see the repeat until structure inside the loop, It is difficult to understand how someone would prefer this workaround than just having an additional keyword. – Julian Martin Del Fiore Jan 15 '23 at 07:46
18

The first part is answered in the FAQ as slain pointed out.

As for a workaround, you can wrap the body of the loop in a function and return early from that, e.g.

-- Print the odd numbers from 1 to 99
for a = 1, 99 do
  (function()
    if a % 2 == 0 then
      return
    end
    print(a)
  end)()
end

Or if you want both break and continue functionality, have the local function perform the test, e.g.

local a = 1
while (function()
  if a > 99 then
    return false; -- break
  end
  if a % 2 == 0 then
    return true; -- continue
  end
  print(a)
  return true; -- continue
end)() do
  a = a + 1
end
Community
  • 1
  • 1
finnw
  • 47,861
  • 24
  • 143
  • 221
  • 22
    Please don't. You create closure environment on each iteration and this is HUGE waste of memory and GC cycles. – Oleg V. Volkov Dec 11 '12 at 17:22
  • 5
    go check `collectgarbage("count")` even after your simple 100 tries and then we'll talk. Such "premature" optimization saved one highload project from rebooting every minute last week. – Oleg V. Volkov Dec 12 '12 at 10:26
  • 6
    @OlegV.Volkov while this example does put a relatively high load on the GC, it does not leak - All the temporary closures will be collected. I don't know about your project but IME most repeating reboots are due to leaks. – finnw Dec 12 '12 at 13:22
17

I've never used Lua before, but I Googled it and came up with this:

http://www.luafaq.org/

Check question 1.26.

This is a common complaint. The Lua authors felt that continue was only one of a number of possible new control flow mechanisms (the fact that it cannot work with the scope rules of repeat/until was a secondary factor.)

In Lua 5.2, there is a goto statement which can be easily used to do the same job.

Community
  • 1
  • 1
slain
  • 187
  • 2
15

Lua is lightweight scripting language which want to smaller as possible. For example, many unary operation such as pre/post increment is not available

Instead of continue, you can use goto like

arr = {1,2,3,45,6,7,8}
for key,val in ipairs(arr) do
  if val > 6 then
     goto skip_to_next
  end
     # perform some calculation
  ::skip_to_next::
end
8

We can achieve it as below, it will skip even numbers

local len = 5
for i = 1, len do
    repeat 
        if i%2 == 0 then break end
        print(" i = "..i)
        break
    until true
end

O/P:

i = 1
i = 3
i = 5
Dilip
  • 628
  • 2
  • 10
  • 23
6

We encountered this scenario many times and we simply use a flag to simulate continue. We try to avoid the use of goto statements as well.

Example: The code intends to print the statements from i=1 to i=10 except i=3. In addition it also prints "loop start", loop end", "if start", and "if end" to simulate other nested statements that exist in your code.

size = 10
for i=1, size do
    print("loop start")
    if whatever then
        print("if start")
        if (i == 3) then
            print("i is 3")
            --continue
        end
        print(j)
        print("if end")
    end
    print("loop end")
end

is achieved by enclosing all remaining statements until the end scope of the loop with a test flag.

size = 10
for i=1, size do
    print("loop start")
    local continue = false;  -- initialize flag at the start of the loop
    if whatever then
        print("if start")
        if (i == 3) then
            print("i is 3")
            continue = true
        end

        if continue==false then          -- test flag
            print(j)
            print("if end")
        end
    end

    if (continue==false) then            -- test flag
        print("loop end")
    end
end

I'm not saying that this is the best approach but it works perfectly to us.

winux
  • 452
  • 4
  • 12
4

Again with the inverting, you could simply use the following code:

for k,v in pairs(t) do
  if not isstring(k) then 
    -- do something to t[k] when k is not a string
end
8lakester
  • 41
  • 3
  • 3
    The problem with inversion is that more often than not there are multiple conditionals in a series (such as to validate user input). And because there might need to be a short circuit at any point along the way, inversion means having to nest the conditionals continuously (instead of "is this bad? then escape; else is this bad? then escape", which is very straightforward, you end up with code like "is this okay? then is this okay? then is this okay? then do this" which is very excessive. – Leslie Krause Jan 23 '19 at 01:19
-16

Why is there no continue?

Because it's unnecessary¹. There's very few situations where a dev would need it.

A) When you have a very simple loop, say a 1- or 2-liner, then you can just turn the loop condition around and it's still plenty readable.

B) When you're writing simple procedural code (aka. how we wrote code in the last century), you should also be applying structured programming (aka. how we wrote better code in the last century)

C) If you're writing object-oriented code, your loop body should consist of no more than one or two method calls unless it can be expressed in a one- or two-liner (in which case, see A)

D) If you're writing functional code, just return a plain tail-call for the next iteration.

The only case when you'd want to use a continue keyword is if you want to code Lua like it's python, which it just isn't.²

What workarounds are there for it?

Unless A) applies, in which case there's no need for any workarounds, you should be doing Structured, Object-Oriented or Functional programming. Those are the paradigms that Lua was built for, so you'd be fighting against the language if you go out of your way to avoid their patterns.³


Some clarification:

¹ Lua is a very minimalistic language. It tries to have as few features as it can get away with, and a continue statement isn't an essential feature in that sense.

I think this philosophy of minimalism is captured well by Roberto Ierusalimschy in this 2019 interview:

add that and that and that, put that out, and in the end we understand the final conclusion will not satisfy most people and we will not put all the options everybody wants, so we don’t put anything. In the end, strict mode is a reasonable compromise.

² There seems to be a large number of programmers coming to Lua from other languages because whatever program they're trying to script for happens to use it, and many of them want don't seem to want to write anything other than their language of choice, which leads to many questions like "Why doesn't Lua have X feature?"

Matz described a similar situation with Ruby in a recent interview:

The most popular question is: "I’m from the language X community; can’t you introduce a feature from the language X to Ruby?", or something like that. And my usual answer to these requests is… "no, I wouldn’t do that", because we have different language design and different language development policies.

³ There's a few ways to hack your way around this; some users have suggested using goto, which is a good enough aproximation in most cases, but gets very ugly very quickly and breaks completely with nested loops. Using gotos also puts you in danger of having a copy of SICP thrown at you whenever you show your code to anybody else.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
DarkWiiPlayer
  • 6,871
  • 3
  • 23
  • 38
  • 18
    I downvoted because the very first sentence is obviously false, and the rest of the answer is unhelpful. – bfontaine Jan 05 '20 at 21:14
  • 3
    Unhelpful? Maybe; it's a somewhat opinion-based answer. The first sentence is obviously true though; `continue` might be a convenient feature, but that doesn't make it *necessary*. Lots of people use Lua just fine without it, so there's really no case for it being anything else than a neat feature that's not essential to any programming Language. – DarkWiiPlayer Jan 06 '20 at 09:00
  • 11
    That’s not an argument: you can’t argue that people are "fine without it" when they don’t have any choice. – bfontaine Jan 06 '20 at 10:45
  • 1
    I think we just have different definitions of "necessary" then. – DarkWiiPlayer Jan 06 '20 at 13:11
  • 7
    repeat/until is not necessary either. Neither is for loop, string concat operator, named function syntax, and countless others. To consider whether the feature is "unnecessary" in the sense mentioned is simply off topic. – Sylvain Hubert Apr 24 '21 at 13:17
  • @SylvainHubert Yet that seems to be exactly the kind of reasoning that decides whether or not a syntax feature makes into the languages, and so far, `continue` isn't a part of it, so it seems safe to say it's not being considered as necessary as the `repeat` and `for` loops. – DarkWiiPlayer Apr 26 '21 at 11:48
  • 1
    @DarkWiiPlayer it's safe to say that only when the reasoning is solid enough, which seems to be a really delicate assumption. – Sylvain Hubert Apr 27 '21 at 16:37
  • 3
    "necessary". No high level language is "necessary", you can do everything in machine language. But when you *do* have a high level language, other factors come into play. For example, readability. – Jürgen A. Erhard Aug 01 '21 at 03:58
  • 2
    @JürgenA.Erhard yes, but that's a tradeoff. It's no secret that Lua aims primarily for small syntax (it fits on one page in BNF). Special `continue` syntax would add almost nothing to the language at the cost of an additional keyword which just isn't in line with Luas design philosophy, so in that sense, it isn't "necessary" for the language to achieve its goal. – DarkWiiPlayer Aug 02 '21 at 11:37