lua-users home
lua-l archive

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



On 14/08/2007, at 2:07 AM, Tom Miles wrote:

I need to read up on environments as I don't really understand what
setfenv does.

The first thing to understand about environments is that Lua doesn't really have a concept of global variables. With the exception of variables declared "local" and upvalues, variables are looked up in the current environment. Effectively, the current environment is the "root" of the variables tree. As an example:

a = 2

Is really like doing this:

_G.a = 2

Where _G is a convenience variable created by Lua to refer to "the current environment". However you could delete _G, and the statement "a = 2" would still work:

_G = nil
a = 2

What Lua is really doing when you say "a = 2" is:

* fetch the current environment table
* assign 2 to be the value of key "a" in that table

You can see that _G and the current environment are the same thing by doing this:

--------

> print (_G)
table: 0x300350
> print (getfenv ())
table: 0x300350
>

--------

Now the powerful thing about environments is you can make "sandboxes" by changing the environment. This small example shows how you can eliminate all existing "global" variables:

--------

function f ()
  print ("hello")
end -- f

f ()   --> prints "hello"

t = {}  -- some new environment

setfenv (f, t)

f ()  --> error: attempt to call global 'print' (a nil value)

--------


After the setfenv the function f, which worked when called the first time, no longer can find the function "print".

I could add the single "global" variable "print" to my new environment like this:

--------

function f ()
  print ("hello")
end -- f

f ()  --> prints "hello"

t = { print = print }  -- some new environment

setfenv (f, t)

f ()  --> prints "hello"

--------

Now the environment "t" has the function "print" in it, with the value from the original environment.

An interesting extension to this idea is to make *all* of the original global variables available to the new environment, by using a metatable:

--------

function f ()
  print ("hello")
end -- f

f ()  --> prints "hello"

t = { }  -- some new environment

setmetatable (t, { __index = getfenv () })

setfenv (f, t)

f ()  --> prints "hello"

--------

This example adds a metatable to the new environment, which looks up variables which we are trying to read from, and don't exist, in the original environment. This is effectively what package.seeall does - adds read-access to the global environment, for your module.


Does your approach work with sharing data across
environments?

The "module" function call creates an environment, so the trick to sharing environments is to work out what you want to do exactly. The purpose of doing it (the module call) in the first place is to have local data (in the module's environment) that does not corrupt or affect similarly named data in other modules.

I think the problem with modules loading submodules is that the sharing is a level removed from the real "global" environment. A few debugging prints of various tables should show which module uses which environment.

I think you will find that if you load "utilities" early on (in your main state) then attempts to load it in sub-modules will find an already-existing utilities entry in the main environment, and therefore not create one in the sub-module environment. This may well solve your variable-sharing problem.

- Nick