Goto Statement

lua-users home
wiki

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 9:27 am GMT (diff)