Function/variable scope (pass by value or reference?)

50,830

Solution 1

You guessed right, table variables are passed by reference. Citing Lua 5.1 Reference Manual:

There are eight basic types in Lua: nil, boolean, number, string, function, userdata, thread, and table. ....

Tables, functions, threads, and (full) userdata values are objects: variables do not actually contain these values, only references to them. Assignment, parameter passing, and function returns always manipulate references to such values; these operations do not imply any kind of copy.

So nil, booleans, numbers and strings are passed by value. This exactly explains the behavior you observe.

Solution 2

Lua's function, table, userdata and thread (coroutine) types are passed by reference. The other types are passed by value. Or as some people like to put it; all types are passed by value, but function, table, userdata and thread are reference types.

string is also a kind of reference type, but is immutable, interned and copy-on-write - it behaves like a value type, but with better performance.

Here's what's happening:

local a = 9
local t = {4,6}

function moda(a)
  a = 10 -- sets 'a', which is a local introduced in the parameter list
end

function modt(t)
  t[1] = 7 -- modifies the table referred to by the local 't' introduced in the parameter list
  t[2] = 8
end

Perhaps this will put things into perspective as to why things are the way they are:

local a = 9
local t = {4,6}

function moda()
  a = 10 -- modifies the upvalue 'a'
end

function modt()
  t[1] = 7 -- modifies the table referred to by the upvalue 't'
  t[2] = 8
end

-- 'moda' and 'modt' are closures already containing 'a' and 't',
-- so we don't have to pass any parameters to modify those variables
moda()
modt()
print(a)  -- now print 10
print(t[1]..t[2])  -- still print 78

Solution 3

jA_cOp is correct when he says "all types are passed by value, but function, table, userdata and thread are reference types."

The difference between this and "tables are passed by reference" is important.

In this case it makes no difference,

function modt_1(x)
  x.foo = "bar"
end

Result: both "pass table by reference" and "pass table by value, but table is a reference type" will do the same: x now has its foo field set to "bar".

But for this function it makes a world of difference

function modt_2(x)
  x = {}
end

In this case pass by reference will result in the argument getting changed to the empty table. However in the "pass by value, but its a reference type", a new table will locally be bound to x, and the argument will remain unchanged. If you try this in lua you will find that it is the second (values are references) that occurs.

Solution 4

I won't repeat what has already been said on Bas Bossink and jA_cOp's answers about reference types, but:

-- since it's define local, should not have func scope

This is incorrect. Variables in Lua are lexically scoped, meaning they are defined in a block of code and all its nested blocks.
What local does is create a new variable that is limited to the block where the statement is, a block being either the body of a function, a "level of indentation" or a file.

This means whenever you make a reference to a variable, Lua will "scan upwards" until it finds a block of code in which that variable is declared local, defaulting to global scope if there is no such declaration.

In this case, a and t are being declared local but the declaration is in global scope, so a and t are global; or at most, they are local to the current file.

They are then not being redeclared local inside the functions, but they are declared as parameters, which has the same effect. Had they not been function parameters, any reference inside the function bodies would still refer to the variables outside.

There's a Scope Tutorial on lua-users.org with some examples that may help you more than my attempt at an explanation. Programming in Lua's section on the subject is also a good read.

Solution 5

Does this mean that table variables are passed to the function by reference and not value?

Yes.

How is the global variable creation conflicting with the already define local variable?

It isn't. It might appear that way because you have a global variable called t and pass it to a function with an argument called t, but the two ts are different. If you rename the argument to something else, e,g, q the output will be exactly the same. modt(t) is able to modify the global variable t only because you are passing it by reference. If you call modt({}), for example, the global t will be unaffected.

Why is modt able to modify the table yet moda is not able to modify the a variable?

Because arguments are local. Naming your argument a is similar to declaring a local variable with local a except obviously the argument receives the passed-in value and a regular local variable does not. If your argument was called z (or was not present at all) then moda would indeed modify the global a.

Share:
50,830

Related videos on Youtube

frooyo
Author by

frooyo

Updated on January 11, 2020

Comments

  • frooyo
    frooyo over 4 years

    I'm completely confused by Lua's variable scoping and function argument passing (value or reference).

    See the code below:

    local a = 9        -- since it's define local, should not have func scope
    local t = {4,6}    -- since it's define local, should not have func scope
    
    function moda(a)
      a = 10           -- creates a global var?
    end
    function modt(t)
      t[1] = 7         -- create a global var?
      t[2] = 8
    end
    
    moda(a)
    modt(t)
    print(a)  -- print 9 (function does not modify the parent variable)
    print(t[1]..t[2])  -- print 78 (some how modt is modifying the parent t var) 
    

    As such, this behavior completely confuses me.

    • Does this mean that table variables are passed to the function by reference and not value?

    • How is the global variable creation conflicting with the already define local variable?

      • Why is modt able to modify the table yet moda is not able to modify the a variable?
  • Michael Anderson
    Michael Anderson over 12 years
    This is subtly different to pass-by-reference. (See my answer). In particular the behaviour for function(x) x={} end is different.
  • Ethan
    Ethan over 8 years
    I've found a nice way to think about this is just that everything is passed by value. However it just so happens that some types are just references. The references themselves are passed by value which is why your example does not alter 't'. Good explanation :)
  • Ethan
    Ethan over 8 years
    Everything is pass by value, by certain types (tables, functions, threads and (full) userdata values) are references. These references are passed by value.