luaupvalue

Lua upvalues not behaving as expected with local variables


Consider this Lua 5.1 code:

function foo ()
 function makeAdder (withWhat)
   return function (a)
     return a + withWhat
   end
 end -- makeAdder
 f1 = makeAdder (6)
 f2 = makeAdder (7)
end -- for

foo ()
print (f1 (2))  --> 8
print (f2 (2))  --> 9

The functions f1 and f2 have 6 and 7 as upvalues to the closures, and thus output 8 and 9 in response to being called. This is what I expect.

Now consider:

function foo ()
 local withWhat
 function makeAdder ()
   return function (a)
     return a + withWhat
   end
 end -- makeAdder

 withWhat = 6
 f1 = makeAdder ()
 withWhat = 7
 f2 = makeAdder ()
end -- for

foo ()
print (f1 (2))  --> 9
print (f2 (2))  --> 9

The functions f1 and f2 have 6 and 7 as upvalues to the closures at the time they are created, however they output 9 and 9, which is not what I expect.

I thought f1 and f2 would be closed with their upvalues at the point of their creation, but obviously not. How does 7 become an upvalue for f1, even though it "should" have been 6?

Lua version 5.1.5.


Solution

  • Upvalues of a inner function in lua are copied by address from the enclosing function, resulting in both f1 and f2's withWhat upvalues in your second code pointing to the same address.

    There are actually two ways to copy:

    1. If an upvalue of the inner function refers to an upvalue of the enclosing function, lua simply copies the pointer.

      ncl->l.upvals[j] = cl->upvals[GETARG_B(*pc)];
      

      This is the case with your second piece of code. As for both makeAdder and function(a), withWhat is their upvalue, so the withWhat variable in both functions point to the same address.

    2. If an upvalue of the inner function refers to a local variable of the enclosing function, lua attempts to find the corresponding upvalue from the list of created upvalues in the current stack, it will create a new one if the find fails. (See luaF_findupval)

      ncl->l.upvals[j] = luaF_findupval(L, base + GETARG_B(*pc));
      

      This is the case with your first piece of code. withWhat is a variable of the makeAdder function and an upvalue of the function(a) function, so lua will create a new upvalue for the function(a) function to store withWhat.

      If you have another function inside the makeAdder function that uses the withWhat variable, lua will reuses the created upvalue. Consider the code:

      function foo ()
       function makeAdder (withWhat)
         return function (a)
           local b = a + withWhat
           withWhat = withWhat + 10
           return b
         end, function (a)
           return a + withWhat --Will be affected by the above function
         end
       end
      
       f1, f2 = makeAdder (6)
      end
      
      foo ()
      print (f1 (2))  --> 8
      print (f2 (2))  --> 18