Goto Statement |
|
goto
statement was added in Lua 5.2.0-beta-rc1 [6] [1] and refined in 5.2.0-beta-rc2 [7]. This is a restrictive form of goto
in that
This rest of this page explores some usages of this new construct.
-- 5.2.0-beta-rc2 for z=1,10 do for y=1,10 do for x=1,10 do if x^2 + y^2 == z^2 then print('found a Pythagorean triple:', x, y, z) goto done end end end end ::done::
-- 5.2.0-beta-rc2 for z=1,10 do for y=1,10 do for x=1,10 do if x^2 + y^2 == z^2 then print('found a Pythagorean triple:', x, y, z) print('now trying next z...') goto zcontinue end end end ::zcontinue:: end
See also ContinueProposal.
-- Lua 5.2.0-beta-rc2 for x=1,5 do ::redo:: print(x .. ' + 1 = ?') local y = tonumber(io.read'*l') if y ~= x + 1 then goto redo end end
-- Lua 5.2.0-beta-rc2 for _, x in ipairs(t) do if x % 2 == 0 then print 'list has even number' goto has end end print 'list lacks even number' ::has::
-- Lua 5.1 equivalent local has for _, x in ipairs(t) do if x % 2 == 0 then has = true break end end if has then print 'list has even number' else print 'list lacks even number' end
-- 5.2.0-beta-rc1 ::a:: print 'A' if math.random() < 0.3 then goto c end ::b:: print 'B' if math.random() < 0.5 then goto a end ::c:: print 'C' if math.random() < 0.1 then goto a else goto b end
See also code generation discussions [8].
Lua already has ProperTailRecursion, but in the hypothetical case it did not, we could simulate tail calls with goto
(as some of the C source code of Lua does):
-- 5.2.0-beta-rc2 - factorial with tail recursion simulated with goto's -- (warning: there's no need to do this) function fact_(n, ans) ::call:: if n == 0 then return ans else n, ans = n - 1, ans * n goto call end end print(fact_(5, 1)) --> 120
-- 5.2.0-beta-rc2 function f() if not g() then goto fail end if not h() then goto cleanup_g end if not i() then goto cleanup_h end do return true end -- need do/end? ::cleanup_h:: undo_h() ::cleanup_g:: undo_g() ::fail:: return false
Not possible without computed goto
[4]. See also SwitchStatement.
It may help readability for label names to indicate the direction (up or down)
that they jump. [10] In the example below, it
may be conventionally understood that the names continue
and skip
will jump down and the
name redo
will jump up.
-- 5.2.0-beta-rc2 ::redo:: for x=1,10 do for y=1,10 do if not f(x,y) then goto continue end if not g(x,y) then goto skip end if not h(x,y) then goto redo end ::continue:: end end ::skip::
If you use two such chunks of code in the same scope, you will need to disambiguate the label names (e.g. @redo1:
and @redo2:
) or wrap each in a do/end
block.
Here's some examples of the scoping rules for goto
:
::a:: goto b -- valid (forward jump) goto a -- valid (backward jump) ::b:: goto c -- invalid (jump into nested block prohibited because nested label not even visible here) goto d -- invalid (jump into nested function prohibited because nested label not even visible here) do ::c:: goto a -- valid (backward jump out of nested block) goto e -- valid (forward jump out of nested block) end (function() ::d:: goto a -- invalid (jump out of nested function) end)() do ::e:: end -- valid, but not visible outside the block; above "goto e" sees only next line ::e:: -- valid goto f -- invalid (forward jump into scope of local definition) local x ::f:: goto e -- valid (backward jump across local definition) --::e:: -- this would be invalid (duplicate label in same scope)
Note that you can think of
do <...> --::a:: goto a -- invalid (forward jump into scope of local definition) goto b -- valid (jump out of block) <...> local x <...> ::a:: <...> --goto a ::b:: end
as equivalent to
do <...> --::a:: goto a -- invalid (jump into nested block prohibited because nested label not even visible here) goto b -- valid (jump out of block) <...> do local x <...> ::a:: <...> --goto a end ::b:: end
so, in a way, the rule against "jump into scope of local definition" is implied by the rule against "jump into nested block" (but not vice-versa). However, 5.2.0-beta-rc1 doesn't treat scoping between these two forms exactly analogously: if you add another ::a::
before the goto a
, the former form will generate an error about duplicate label, whereas the latter will not (though it does in rc2) because the nested ::a::
is never seen by a goto
outside the nested block (and any goto
inside the nested block will only see the nested ::a::
).
The particular treatment of labels at the end of the block (::b::
) is what allows a loop continue construct to be implemented (example above) even when the loop block contains locals following the continue.
goto
's can sometimes generate the exact same bytecodes and debuginfo as control structures, except for for
loops:
-- compare.lua -- tested 5.2.0rc1 local FS = require 'file_slurp' -- https://raw.github.com/gist/1325400/0de9b965af138f2fb3d76fc81d97a863f6f409b3/file_slurp.lua local function compile(code) FS.writefile('luac -o luac.out -', code, 'p') local binary = FS.readfile('luac.out') local text = FS.readfile('./src/luac -p -l luac.out', 'p'):gsub('0x[0-9a-fA-F]+', '(address)') return binary, text end local a, at = compile [[ local x = 1 while not(x > 1e8) do x = x + 1 end ]] local b, bt = compile [[ local x = 1 ::a:: if x > 1e8 then goto e end x = x + 1 goto a; ::e:: ]] assert(a == b) assert(at == bt) local a, at = compile [[ if x then f() else g() end ]] local b, bt = compile [[ if not x then goto a end f() goto b; ::a:: g() ::b:: ]] assert(a == b) assert(at == bt) local a, at = compile [[ local sum = 0 for i=1,1E8 do sum = sum + i end ]] local b, bt = compile [[ local sum = 0 local i=1; ::a:: if i > 1E8 then goto b end sum = sum + i; i=i+1 goto a; ::b:: ]] assert(a ~= b) -- these differ significantly and the latter is about twice as slow. assert(at ~= bt) print 'DONE'
(In the earlier 5.2.0beta, when a single goto exists inside a conditional block, some of the JMP's were superfluous [5]).
In 5.2.0-beta-rc1, labels used the syntax @name:
(optionally with spaces, e.g. @ name :
). Restrictions on duplicate label names were different.