Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.

Due to spam on this forum, all posts now need moderator approval.

 Entire forum ➜ MUSHclient ➜ Lua ➜ How to make all variables local to a function

How to make all variables local to a function

It is now over 60 days since the last post. This thread is closed.     Refresh page


Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Sun 03 Sep 2006 06:30 AM (UTC)

Amended on Sun 03 Sep 2006 06:51 AM (UTC) by Nick Gammon

Message
Lua has the default behaviour that all variables are global unless declared local to a block.

Here is a method of reversing that behaviour. For example, PHP has the reverse behaviour, that all variables in a function are local unless you specify otherwise.

First, let's look at the problem. If you write a small function like this, and don't bother with 'local' in front of variables, then all variables you use "pollute" the global address space, and potentially conflict with variables used by other functions.


function test (x)
  a = 1
  b = 2
  c = x * 2
  print (c)
end -- test

test (5) --> 10

print ("after=", a, b, c) --> after=	1	2	10



You can see from this that the variables a, b and c have been changed in global address space.

We can hide the global space from the function with setfenv, like this:


function test (x)
  setfenv (1, {})  --> change environment
  a = 1
  b = 2
  c = x * 2
  print (c)  --> attempt to call global 'print' (a nil value)
end -- test

test (5)

print ("after=", a, b, c)



However we have been too successful here, the function "print" is also hidden. One way around this is to effectively do what PHP does, and force you to declare any global functions you want. You have to do this before the setfenv call ...


function test (x)
  local print = print

  setfenv (1, {})
  a = 1
  b = 2
  c = x * 2
  print (c)
end -- test

test (5) --> 10

print ("after=", a, b, c) --> after=	nil	nil	nil



The line "local print = print" makes a copy of the global variable "print" into a local variable. Now we can print. We could also copy a whole table, like this:


local string = string --> get all string functions


Another approach is to use a metatable to allow us access to global variables for reading, but not for changing. This gives us the advantage of letting us use all existing functions and variables, but we still can't change anything inadvertently:


function test (x)
  local M = {}  
  setmetatable (M, {__index = _G} )
  setfenv (1, M)

  a = 1
  b = 2
  c = x * 2
  print (c)
end -- test

test (5)  --> 10

print ("after=", a, b, c)  --> after=	nil	nil	nil



The earlier approach has the advantage that you explicitly state which global variables you are planning to use, the later approach saves a bit of work.

Another technique again is to force the declaration of variables. This is a useful method, and is used in many programming languages (eg. C). It is useful because it catches inadvertent misspellings of variables.


function test (x)
  local M = {}  
  local mt = {
    __index = _G,
    __newindex = function (t, v) 
                   error ("variable not declared: '" .. v .."'", 2) 
                 end
  } -- end metatable
  
  setmetatable (M, mt)
  setfenv (1, M)
  
  a = 1  --> table_test.lua:13: variable not declared: 'a'
  b = 2
  c = x * 2
  print (c)
end -- test

test (5)

print ("after=", a, b, c)



Now an attempt to write to the variable 'a' raises an error, forcing us to make sure it is declared:


function test (x)
  local M = {}  
  local mt = {
    __index = _G,
    __newindex = function (t, v) 
                   error ("variable not declared: '" .. v .."'", 2) 
                 end

  } -- end metatable
  
  setmetatable (M, mt)
  setfenv (1, M)
  
  local a, b, c
  
  a = 1
  b = 2
  c = x * 2
  print (c)
end -- test

test (5) --> 10

print ("after=", a, b, c) --> after=	nil	nil	nil



However this version still has a problem - we detect changes to "a" but not attempts to read from it (eg. print (a) ). Another change will fix that:


function test (x)
  local print = print

  local M = {} 
  local undec = function (t, v) 
                   error ("variable not declared: '" .. v .."'", 2) 
                 end 
  local mt = {
    __index = undec,
    __newindex = undec
  } -- end metatable
  
 
  setmetatable (M, mt)
  setfenv (1, M)
  
  print (a)   --> table_test.lua:16: variable not declared: 'a'
  a = 1
  b = 2
  c = x * 2
  print (c)
end -- test

test (5)

print ("after=", a, b, c)



I had to put back the line "local print = print" because the new metamethod for __index now throws an error, so we have to explicitly declare which global variables we want.


function test (x)
  local print = print
  
  local M = {} 
  local undec = function (t, v) 
                   error ("variable not declared: '" .. v .."'", 2) 
                 end 
  local mt = {
    __index = undec,
    __newindex = undec
  } -- end metatable
  
  setmetatable (M, mt)
  setfenv (1, M)
  
  local a, b, c
  print (a)  
  a = 1
  b = 2
  c = x * 2
  print (c)
end -- test



This version now works - we have declared a, b, and c.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #1 on Sun 03 Sep 2006 06:56 AM (UTC)
Message
Assuming we might want to do this a lot, we can encapsulate the main behaviour in its own function and use that:



function force_declarations ()

  local M = {} 
  local undec = function (t, v) 
                   error ("variable not declared: '" .. v .."'", 2) 
                 end 
  local mt = {
    __index = undec,
    __newindex = undec
  } -- end metatable
  
  setmetatable (M, mt)
  setfenv (2, M)  -- set environment for parent

end -- force_declarations


function test (x)
  -- capture any global variables we want
  local print = print
  
  -- after this we can't access global variables, and must declare local ones
  force_declarations ()
  
  print (a)  --> table_test.lua:24: variable not declared: 'a'
  a = 1
  b = 2
  c = x * 2
  print (c)
end -- test

test (5)

print ("after=", a, b, c)



Now the test function must declare all variables:


function test (x)
  -- capture any global variables we want
  local print = print
  
  -- after this we can't access global variables, and must declare local ones
  force_declarations ()
  
  local a, b, c
  
  print (a)  
  a = 1
  b = 2
  c = x * 2
  print (c)
end -- test


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,133 posts)  Bio   Forum Administrator
Date Reply #2 on Sun 03 Sep 2006 07:24 AM (UTC)
Message
The function force_declarations can be written more compactly, and possibly more confusingly, like this:


function force_declarations ()
  local undec = function (t, v) error ("variable not declared: '" .. v .."'", 2) end 
  setfenv (2, setmetatable ({}, {__index = undec, __newindex = undec}))
end -- force_declarations



This is because setmetatable returns the table you are setting, so it is not really necessary to declare it in a separate variable.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


11,879 views.

It is now over 60 days since the last post. This thread is closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.