Why does Lua have no "continue" statement?

162,017

Solution 1

In Lua 5.2 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

Solution 2

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.

Solution 3

You can wrap loop body in additional repeat until true and then use do break end 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

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

Solution 4

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.)

Solution 5

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
Share:
162,017

Related videos on Youtube

Dant
Author by

Dant

Updated on July 08, 2022

Comments

  • Dant
    Dant almost 2 years

    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?
    • lhf
      lhf over 11 years
      Since this question was asked, Lua got a goto statement which can be used to implement continue. See the answers below.
  • ubershmekel
    ubershmekel over 11 years
    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....
  • RBerteig
    RBerteig over 11 years
    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.
  • udoprog
    udoprog over 11 years
    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).
  • RBerteig
    RBerteig over 11 years
    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 for the relevant threads to get the details. Since they did introduce goto, it obviously was not insurmountable.
  • Oleg V. Volkov
    Oleg V. Volkov over 11 years
    Please don't. You create closure environment on each iteration and this is HUGE waste of memory and GC cycles.
  • Oleg V. Volkov
    Oleg V. Volkov over 11 years
    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.
  • Saurabh
    Saurabh over 11 years
    @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.
  • E. T.
    E. T. over 10 years
    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.
    E. T. over 9 years
    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.
  • Shaun Wilson
    Shaun Wilson almost 9 years
    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: gist.github.com/wilson0x4d/f8410719033d1e0ef771)
  • Glenn Maynard
    Glenn Maynard over 8 years
    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
    Glenn Maynard over 8 years
    (Least of all by "no compelling use cases". If they don't understand the importance of continue, they have no place designing a language.)
  • Oleg V. Volkov
    Oleg V. Volkov about 8 years
    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.
  • Pedro Gimeno
    Pedro Gimeno almost 8 years
    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.
  • David Ljung Madison Stellar
    David Ljung Madison Stellar over 6 years
    I love the admittance: "Both are useful" right after an explanation of "we're not going to do it"
  • user3479901
    user3479901 over 6 years
    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, after 5.2.0 was released.
  • David Ljung Madison Stellar
    David Ljung Madison Stellar over 6 years
    Right - because 'goto' is well-recognized to be a decent programming construct. (end sarcasm) Ah well.
  • neoedmund
    neoedmund about 6 years
    But it did not sound more reasonable than "I just forgot to put continue into Lua, sorry."
  • axkibe
    axkibe almost 6 years
    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...
  • Leslie Krause
    Leslie Krause over 5 years
    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.
  • bfontaine
    bfontaine over 4 years
    I downvoted because the very first sentence is obviously false, and the rest of the answer is unhelpful.
  • DarkWiiPlayer
    DarkWiiPlayer over 4 years
    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.
  • bfontaine
    bfontaine over 4 years
    That’s not an argument: you can’t argue that people are "fine without it" when they don’t have any choice.
  • DarkWiiPlayer
    DarkWiiPlayer over 4 years
    I think we just have different definitions of "necessary" then.
  • DarkWiiPlayer
    DarkWiiPlayer over 4 years
    Nice to see people actually consider performance and even provide luac output on SO! Have a well deserved upvote :)
  • Sylvain Hubert
    Sylvain Hubert about 3 years
    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.
  • DarkWiiPlayer
    DarkWiiPlayer about 3 years
    @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.
  • Sylvain Hubert
    Sylvain Hubert almost 3 years
    @DarkWiiPlayer it's safe to say that only when the reasoning is solid enough, which seems to be a really delicate assumption.
  • Jürgen A. Erhard
    Jürgen A. Erhard over 2 years
    @axkibe Methinks the Lua designers/devs never heard of Dijkstra or even just his (in?)famous "GOTO considered harmful". ;-)
  • Jürgen A. Erhard
    Jürgen A. Erhard over 2 years
    "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.
  • Ankit ihelper Sharma
    Ankit ihelper Sharma over 2 years
    For understanding others, things need to be simple and readable. For usage, everyone has their own approach for the same action.
  • DarkWiiPlayer
    DarkWiiPlayer over 2 years
    @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.
  • Arkt8
    Arkt8 over 2 years
    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.
  • sharat87
    sharat87 over 2 years
    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.
  • FeRD
    FeRD about 2 years
    Fortunately it isn't necessary to write code in a crap language, either. (Not naming any names. But it rhymes with the plural of 'continuum': 'continuua'.)