lua-users home
lua-l archive

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


Hi,

> I'm upgrading from 4.0 to 5.0beta.  One thing I can't really sort out
> after reading the manual and the past mails is the globals.

No surprise.  That section isn't finished in the manual yet and the globals
system has changed _a lot_ since Lua 4.

To begin with there are two sides to globals: the Lua side and the C API
side.

In the C API you can access a globals table that is defined for each
separate lua_State (i.e. each thread has its own.)  To access it you can use
the pseudo index LUA_GLOBALSINDEX:

/* this pushes the globals table of state L */
lua_pushvalue(L, LUA_GLOBALSINDEX);

/* this gets a key in the globals table of L */
lua_pushstring(L, "aap");
lua_gettable(L, LUA_GLOBALSINDEX);  /* global aap */

/* this sets a key in the globals table of L */
lua_pushstring(L, "noot");
lua_pushstring(L, "mies");
lua_settable(L, LUA_GLOBALSINDEX); /* global noot = "mies" */

This globals table is also the globals table in the "main" chunk when
running lua.  It is stored in the lua environment under the name _G.  Note
that _G is just an ordinary global variable and changing its value _does
not_ affect the globals table at all!

Instead, in Lua 5.0 each closure (i.e. function instance or loaded chunk)
holds a private reference to a globals table.  All global references in that
closure are resolved in that table.  Since this table is kept in the
_closure_ it can be different from the C API globals (that are kept in the
lua state.)  There is no way to obtain the C API globals from within lua
itself (not true actually, there is a sneaky way to do it, see note (*) at
the end of this mail.)  Each new closure inherits its globals table from the
closure that creates the new closure.  Example:

msg = "hi there!"

function f()
    return function() print(msg) end
end

g = f()
g()  -- this will print "hi there!"

In this example f receives its globals from the current chunk.  Calling f
returns a new closure so g in turn obtains the globals from f.  Note that
the globals table is attached at _instantiation_ of the new closure.  This
means that the same piece of code can result in closures with different
globals tables if the globals of that code were changed between calls.  Here
is how to obtain or change the table of globals.  There are two ways:

1) access/change globals of a given closure
2) access/change globals at a given stack level

So first of all note that there is no simple replacement for the "globals"
call of Lua 4.  Form 1) is as follows:

-- obtain the globals table of closure
getglobals(closure)

-- change the globals table of closure
setglobals(closure, {print = print, msg = "yo!"})

We continue our example, building on the code above:

g1 = f()
g1()  -- prints "hi there!"

setglobals(f, {print = print, msg = "yo!"})
g2 = f()
g1()  -- still prints "hi there!"
g2()  -- prints "yo!"

setglobals(g1, {msg = "huh?!?"})
-- g1()  -- this will result in an error:  print is a nil value!

Note that the print function is treated like any other global variable.
Form 2) acts on stack levels as follows:

-- set the globals table of _this_ chunk
setglobals(1, {...})

-- set the globals table of the closure that
-- _called_ us (one level up)
setglobals(2, {...})

Similarly getglobals(n) returns the globals table of the closure at stack
level n, where 1 is the innermost closure (that contains the setglobals
call.)

Note: it may look like the pair getglobals(1) / setglobals(1, table) forms a
replacement for the globals call in Lua 4, right?  Wrong!  The big
difference is that any setglobals call in Lua 5.0 will only affect a
_single_ closure!  Especially, it is not possible at all to change the C API
globals with a setglobals call.

(Technical note: there is a problem with getglobals(n) in combination with
tail calls.  The stack does not hold enough information to resolve each
globals table in this case.)

To obtain the same effect as a Lua 4 globals(table) call, you would have to
administrate all existing closures that should have their globals changed
yourself.  That's not easy if not totally impossible.  I think it is fair to
say that this is _the_ most confusing aspect for Lua 4 converts: "globals
are local to a closure."  In fact this "global is local" paradigm is so
confusing that in Lua 5.0 final the globals will be renamed in "function
environments" and the get/setglobals calls will be renamed in get/setfenv.

An example of a call that relies on the caller's globals is "loadfile" to
process a lua script file and return a closure for it (that effectively runs
the script when called.)  The globals that this new closure (or chunk)
obtains are those of the closure that _called_ loadfile.  The loadfile
command does this by doing the equivalent of setglobals(chunk,
getglobals(2)) before returning the chunk.  Otherwise all loaded chunks
would obtain the C API globals by default, because loadfile is a cfunction.

We continue our example:

-- change current globals...
setglobals(1, {
    getglobals = getglobals,
    setglobals = setglobals,
    print = print,
    f = f,
    msg = "cool!"})

print(msg)  -- now prints "cool!"

-- but remember that f holds its _own_ globals table
g = f()
g()  -- this _still_ prints "yo!"

-- try an updated version of f
function f()
    -- change globals to those of the caller...
    setglobals(1, getglobals(2))
    return function() print(msg) end
end

-- change our message
msg = "bye!"

g = f()
g()  -- now it prints "bye!"

msg = "see you!"
g()  -- now it prints "see you!"


I hope that this offers enough of the basics to get you kick-started into
the new Lua 5.0 world!

Bye,
Wim

(*)  As promised:  a recipe to obtain the C API globals in lua.  They key
element is the "dofile" command.  Unlike "loadfile" it does _not_ change the
loaded chunk's globals, so the chunk will run in the C API globals.  Simply
return those and we're done.  We need a simple helper file (since dostring
is no longer available in Lua 5.0):

in file "glob.lua":  return getglobals(1)  -- note:  these will be the C API
globals!

Now in your own script you can simply perform

    c_globals = dofile "glob.lua"

to obtain the C API globals!  I warned you it was sneaky, didn't I!?