Confused with the variable scopes here

Hello
What is the difference between a “local” variable defined directly in the script and a “global” variable defined inside a function.
It seems like we have 2 different variables with the same name “x” here?

local function func2()
	x = 2
end

local function func1()
	print(x)  -- prints nil
	func2()
	print(x)  -- prints 2
end

local x = 1
func1()
print(x)   -- prints 1
1 Like

There is not really a difference in a global and local variable if you put it at the top of the script. Only local variable will only be accessed in that scope(could be entire script if you put it at the top) and global variable in the entire script even if you put it inside of a function.

local x = 1
local function func2()
	x = 2
end

local function func1()
	print(x)  -- should print 1
	func2()
	print(x)  -- should print 2
end

--[[local x = 1.   Should be defined at the top because function will not know variable x until its set above.]]
func1()
print(x)   -- should print 2

I do not put the declaration of the variable at the top of the script however. I want to understand what happens in the case I have given.
It seems that there are at least 2 variables named x here…

This is called Variable Shadowing, and you more likely will see this with local variables than with global ones.

local A = 1
do
	local A = 2 -- A is shadowed as "Hello" for this scope
	print(A)
end
print(A) -- Back to 1

Ideally, you should avoid structuring code like this because it can get confusing, plus the linter will flag it.

I understand your example, but it is different than mine. The linter does not warn me.
I want to understand what is the difference between a global variable defined inside the function and a local variable with the same name defined inside the script

A global variable is really just a globally accessible variable. Your code would be the exact same if you used an unassigned UpValue.

local x -- x is unassigned (or nil)
local function func2()
	x = 2 -- x is now 2
end
local x = 1 -- x got shadowed

The lua parser (which reads your code) goes from top to bottom.

Whenever the parser encounters a function definition, it figures out which variables that function is referring to, based on where it’s defined. This set of variables is called a closure.

First it encounters func2. It creates a closure for func2: It sees that it uses x, and, seeing that there is no x in scope, binds it to a global variable called x.

That means that, whenever func2 is called, from anywhere, the x it manipulates is always going to be the global x, thanks to func2’s closure.

Next the parser encounters func1. It also creates a closure which references the same global x variable as func2. That’s why func1 can observe changes to global x made by func2.

Finally, the parser reads your last three lines. You’re defining a local variable x, but this is NOT the same object that func2 and func1’s closures refer to (i.e. global x).

So when you call func1, which calls func2, they look up x from their closures (defined earlier, when we parsed those function definitions). The fact that there happens to be another local variable with the same name when you go to call them doesn’t matter—it’s a different object.

When you call print at the end, the local x takes precedence over the global x, because it has the tighter scope.

Try moving your local x definition to between the two function definitions, you might see something interesting.

I sort of cover this more here:

1 Like

No, it is not the same.
In your code there is only 1 “global” variable x changing its value
in my code there are 2 “global” variables named x in the global scope at the same time
one stores value 1, the other stores value 2
see:

local function func2()
	x = 2
end

local function func1()
	print(x) 
	func2()
	print(x) 
end

local x = 1
func1() -- prints nil and 2 
print(x)   -- prints 1
func1() -- prints 2 and 2	
print(x)  -- prints 1

so basically we have 2 variables “x” in the global scope? one of them is accessible inside the func1 and func2 and the other is accessible in the main script?

Yes, mostly. The term “global scope” is a little muddy in roblox, though.

See, in normal (non-roblox) lua, global variables are genuinely global. They can be accessed across files. Declaring a global variable (or using one in a function closure like in func2) puts it in a globally accessible table called _G.

So if we weren’t on roblox, and we’re just writing a lua command line app, func2 and func1’s closures would be bound literally to _G["x"]. Functions in other scripts could even bind to the same object. Maybe you can see how messy that can get.

Roblox does things a little differently. Instead of func2 being bound to _G.x, it is bound to a script-global table, which is hidden to us. It’s like _G, but independent to each script.

(Any scripts that want to use _G in roblox can, but have to do so explicitly).

The local x definition is not part of that script-global table (nor _G). Instead, it is a plain old local variable, whose existence begins at the line it is created, and ends at the end of its scope (in this case, the end of the script).

Because local x was defined at the highest scope level of the script (not inside any functions or statements), it will forever shadow (hide) the script-global x, and you’ll never be able to access script-global x again, except through closures that were defined before that local x definition (e.g. func2()).

All that’s to say “yes, there are two xs in play after the line you define the second one”.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.