lua-users home
lua-l archive

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


It seems like what is really wanted is a way to clean up a scope when it closes, so it seems like we should just do that. Create a new block frame, such as "defer ... end" or "exiting ... end". Everything that is in scope at the point where the block begins is in scope throughout the block. However, the contents of the block are not executed inline; rather, once execution reaches the point in the code where the block begins, a deferred-execution guarantee is made for the contained code, which will be executed when the scope immediately containing the exiting ... end block is exited (in newest-guarantee-first order).

function foo(bar, ...)
  local baz = bar exiting
    baz.closed = true
  end
  do 
    local quoz = {...} exiting
      setmetatable(quoz, {__mode='kv'})
    end
    quoz[0] = bar.file or error ("'file' field missing from supplied parameter")
    local moz = io.open(quoz[0], 'a+') exiting 
      moz:close()
    end
    if riz(moz, baz) then -- some global function
      return true
    end
  end
  print("warning: operation could not complete")
  return false
end

So let's break this down a little:

After the local baz is assigned from bar, a guarantee is made that baz.closed will be set to true. This will happen even if bar has been changed.

Inside the do block quoz is assigned to a new table created from varargs. A guarantee is then made that before execution exits the do block, this table will receive a metatable that makes its references weak. This will happen BEFORE baz.closed is set.

A new key is added to this table provided that bar.file is not nil or false (it is probably more typical to use assert here, but this still illustrates the principle). If the error is thrown, quoz will get its metatable, then baz.closed will become true, then the error will propagate out of the function. (However, the code just after this, to call moz:close(), will not be called at all, as execution never reached its deferral.)

Provided there is no error, moz is assigned to a newly opened file. A guarantee is then made that this file will be closed before the do block can exit, before the metatable is applied to quoz (this guarantee of reverse execution is something I believe is well-understood to be necessary to keep code maintainable and deterministic). Then, if the riz funciton returns some true value, moz is closed, quoz is set to weak references, baz.closed is set, and execution then leaves foo with the return value true.

If riz returns a false value, execution proceeds out of the do block. moz is closed and quoz is metatabled. The print statement is executed. Finally, returning false triggers setting baz.closed to true before execution returns to the calling function.

The exiting block is not actually part of the assignment syntax. So 

local baz = bar exiting
  baz.closed = true
end

is the same as 

local baz = bar; exiting
  baz.closed = true
end

which is the same as 

local baz = bar
exiting
  baz.closed = true
end

And there is also no reason you can't have an exiting block without any associated variables.