lua-users home
lua-l archive

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


On Mon, Jun 1, 2009 at 01:50, Peter Cawley <lua@corsix.org> wrote:
> On Sun, May 31, 2009 at 11:22 PM, Cosmin Apreutesei
> <cosmin.apreutesei@gmail.com> wrote:
>> On Mon, Jun 1, 2009 at 00:58, Peter Cawley <lua@corsix.org> wrote:
>>> On Sun, May 31, 2009 at 10:42 PM, Cosmin Apreutesei
>>> <cosmin.apreutesei@gmail.com> wrote:
>>>> With immediately-collected resources, the functionality of f:close()
>>>> remains, as replaced by f = nil. I don't think any control is taken
>>>> away.
>>>>
>>>
>>> Expect "f = nil" would not provide the solid guarantee that the
>>> resource was collected, whereas "f:close()" does. Consider the example
>>> of a database API which only allows one (non-closed) cursor to exist
>>> at any one time. After running a query and using a cursor to iterate
>>> the results, you want to be absolutely sure that the cursor is
>>> collected, as otherwise no other database calls will work. If you
>>> accidently make a copy of "f" to some place, then "f = nil" would not
>>> collect the cursor, and your application would start failing all over
>>> the place. Using "f:close()" would guarantee that the cursor is
>>> closed, and any further operations on this one cursor object would
>>> result in a Lua error, which is (IMO) a much better result than having
>>> the rest of the application's future DB interactions failing.
>>
>> But that's a feature any library could provide if so designed. That
>> database API would surely have a close() method. And if it doesn't,
>> you could encapsulate the handler with a few lines of lua code and
>> provide the close() method yourself.
>>
> Consider the simple implementation of a hypothetical DB handling function:
>
> function DoStuff()
>  local query = DB.execute"select * from blah"
>  for row in query do
>    -- do something with row
>  end
>  query:close()
>
>  query = DB.execute"select * from meh"
>  -- etc.
>  query:close()
> end
>
> This is fine, unless the "do something with row" code causes an error
> to be thrown, or includes a "return" statement, at which point, the
> execution of DoStuff is terminated, and "query:close()" is never run.
> The next iteration might then look something like the following:
>
> function DoStuff()
>  local query = DB.execute"select * from blah"
>  local status, err = pcall(function()
>    for row in query do
>      -- do something with row
>      -- NB: return statements no longer cause DoStuff to return
>    end
>  end)
>  query:close()
>  if err then error(err) end -- NB: location of error moved, stack
> trace will be affected
>
>  query = DB.execute"select * from meh"
>  -- etc.
>  query:close()
> end
>
> This implementation would cause "query:close()" to run even if "do
> something with row" causes an error, or if it contains a return
> statement. It still has a number of shortcomings though:
>  * return statements within the for loop no longer do the same thing
> as they did before
>  * the location at which the error happens has changed, which will
> affect any debug stack traces
>  * the resource clean-up code is still separated from resource
> acquisition (image the "do something with row" code being very long)
>  * the code complexity has increased a fair bit
>  * the "do something with row" code cannot call coroutine.yield, as
> it would be yielding across a C call (the pcall)
>
> You could consider another Lua solution which would preserve the
> behaviour of return statements, but this would increase the complexity
> significantly (a robust soluation would have to save a vararg list of
> the return values and all sorts of other stuff). On the other hand,
> consider a hypothetical language-level "with" construct:
>
> function DoStuff()
>  local query = DB.execute"select * from blah"
>  with query do
>    for row in query do
>      -- do something with row
>      -- NB: return statements work as intended, and error location
> information is preserved
>    end
>  end
>
>  query = DB.execute"select * from meh"
>  -- etc.
>  query:close()
> end
>
> This achieves the initial goal of ensuring that query:close() is
> called, regardless of how execution leaves the for loop. It does so in
> a short and elegant manner, addressing all of the shortcomings of the
> previous method.
>

The 'with' construct has been reviewed already, and also got critiqued.