lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


In Lua 5.2.0-beta-rc4, gotos allow transactional error handling code
to be written something like this:

  -- 5.2.0-beta-rc2
  function perform_transaction()
    if not f() then goto undone_f end  -- [*2]
    if not g() then goto undone_g end  -- [*2]
    local hval = h()
    if not hval then goto undone_h end  -- [*2]

    local x
    .....

    return true  -- success [*1]

    -- rollback
    ::undone_h::
    undo_g()  ::undone_g::
    undo_f()  ::undone_f::
    print(x)

    return false  -- failure
  end

except for two issues.  First, the "return true" is required to be
wrapped in do/end [*1].  Secondly, and more importantly, the gotos
will be rejected as written because they jump into the scope of locals
[*2] ....or do they?  The reference manual narrows variable scope
slightly:

  "The scope of a local variable begins at the first statement after
its declaration and lasts until the last non-void statement of the
innermost block that includes the declaration."

thereby permitting things like continue: `whlie ..... goto continue
..... local x ..... ::continue:: end`.  It would be possible though to
tighten the scope rules further to allow the error handling code
above.  The new rule would be that the scope of a local ends at a
label that is jumped to from outside that variable's scope.
Therefore, the `print(x)` above will print nil.

However, the Lua 5.1 way of doing all this may still be easier to understand:

  function perform_transaction()
    local fval, gval, hval
    repeat
      fval = f(); if not fval then break end
      gval = g(); if not gval then break end
      hval = h(); if not hval then break end  -- alternately `goto`
the precise rollback statement below
      local x
      .....
      return true
    until false  -- an abuse of repeat; alternately use `do ..... goto
last ..... end ::last::`

    -- rollback
    if gval then undo_g() end -- alternately add labels here to jump to
    if fval then undo_f() end

    return false  -- failure
  end

There are some redundant conditionals in the rollback code, which can
now be eliminated with gotos again, but as with the case with
exception handling, we can normally assume that traversal of the error
handling code path is rare and not performance critical.

Now, more ideally, a macro preprocessor could transform the below code
into the above:

  function perform_transaction()
    ROLLBACK.FAIL return false
    CHECK.FAIL f(); ROLLBACK.FAIL undo_f()
    CHECK.FAIL g(); ROLLBACK.FAIL undo_g()
    hval = h(); CHECK.FAIL hval
    local x
    .....
    return true
  end

The idea is that the ROLLBACK prefixed statements are normally
ignored.  However, if the conditional in a CHECK.X statement is false,
then any ROLLBACK.X statements above it are immediately executed in
reverse order.  To take another example, this:

  function read(filename)
    ROLLBACK.FAIL return nil, err
    local fh, err = io.open(filename); CHECK.FAIL fh; ROLLBACK.ALWAYS fh:close()
    return fh:read()
  end

would be translated into

  function read(filename)
    local fh, err = io.open(filename)

    -- CHECK.FAIL fh
    if not(fh) then goto fail1 end

    -- return fh:read(), part 1 of 2
    local _tmp1 = return fh:read()

    -- ROLLBACK.ALWAYS
    fh:close()

    -- return fh:read(), part 2 of 2
    return _tmp1

    ::fail1::
    -- ROLLBACK.FAIL
    return nil, err
  end