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