Goto Statement


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

Nested break

-- 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::

Continue or nested continue

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

Perl style redo [2]

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

Pythonic for-else [3]

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

State machine or Markov chain

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

Simulated tail call

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

Error handling and cleanup

-- 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
(from [9])

Switch statement

Not possible without computed goto [4]. See also SwitchStatement.

Conventions for Labels

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.

Scoping

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.

Efficiency

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

Changes

In 5.2.0-beta-rc1, labels used the syntax @name: (optionally with spaces, e.g. @ name :). Restrictions on duplicate label names were different.


RecentChanges · preferences
edit · history
Last edited February 2, 2014 3:27 am GMT (diff)

AltStyle によって変換されたページ (->オリジナル) /