When do i use local and non-local functions?

When do I use them do I even use local function what the difference and Especially When do I USE Them?

Is this the same as this?

local function F()
end

local F = function()
end

and is this the same as this?

function F()
end

F = function()
end

Please tell me

1 Like

As far as I know, local variables are limited to their scope.

So, if you write:

local function F()
    local var = 6
end

F()

print(var) --prints nil

So, if you make a function inside a function, and make the function inside, local, then you wouldn’t be able to call the local function outside of the parent function.

local function F()
    local function AnotherF()
    end
end

F()

AnotherF() --error here

If you make that function, global, then calling the parent function will be enough. After that, you are able to call the child function outside of the parent function.

local function F()
    function AnotherF()
    end
end

 F()

AnotherF() --no error here

Regarding your examples, yes

local function F()
end
---same as
local F = function()
end

Edit: I may be wrong, this is the explanation I’ve been finding on this forum a lot, and I haven’t actually checked if it’s correct.

Edit2: I just checked, it is correct

Edit3: I found even more information on this. It turns out, local functions and variables are actually accessed faster than global ones, because they are stored on the stack. However, the main reason to use local variables/functions is that it prevents name collision.

3 Likes

They don’t prevent collisions, necessarily. Local variables can “shadow” variables in a higher scope, which is more often than not unintentional!

As for actually USING local functions… for all intents and purposes they’re the same as global functions but very slightly faster as Soflowsen said. There is some nuance to when you explicitly need a function to be global, but that nuance is irrelevant in Roblox as you can’t directly require non-ModuleScripts.

Local variables can “shadow” global variables, but not the other way around.

It actually woudn’t error, when you try to do something with an variable which doesn’t exist, it will just return nil;
Unless you tried to index something on that variable, then it would.

1 Like

It’s already been asked, What is the difference between a local function and a normal function? - #2 by AwesomePossum212

1 Like

They can shadow any variable in a higher scope, not only global variables.

Nope I said when do I use them not whats the difference

Whenever you want to use them.

Since functions are treated like any other value type in Lua, they can be assigned to variables and returned from other functions. They can also be attached to a scope with variables that follow that scope, making it a closure.

I have an implementation of the flood-fill algorithm that’s supposed to be as reusable as possible. For that purpose I’ve implemented it as a function that takes functions as arguments that define the graph the algorithm works on, as well as how it does it. The signature looks something like

function flood_fill(start_cell, f_get_neighbors, f_add_to_closed)
end

where the parameter names starting with “f_” indicate that they’re supposed to be functions. A call to flood_fill could look like

local closed_set = Map3D.new() --a set of Vector3s
flood_fill(
    start_cell,
    function(cell) --f_get_neighbors
        return {
            Vector3.New(1, 0, 0),
            Vector3.New(-1, 0, 0),
            Vector3.New(0, 0, 1),
            Vector3.New(0, 0, -1)
        } --for a square grid, any other logic could be used to define any kind of graph
    end,
    function(cell) --f_add_to_closed
        closed_set:vSet(cell, true)
    end
)

Another pattern I sometimes use is factories. Factories make something. Often what they make is a function.

function fractalNoiseFactory(params)
	--[[
	Returns a fractal noise generator
	]]	
	local noise = math.noise
	local seed = params.seed or (e * r:NextInteger(-RANDOM_SEED_AMPLITUDE, RANDOM_SEED_AMPLITUDE))
	local amplitude = params.amplitude or 1
	
        ... --other local variables
	
	local function evaluate(x, y, z)
		... --does something
	end
    return evaluate
end

Local functions defined inside other functions/scopes so as to make use of the parent scope’s context are known as closures.

local function F()
end
local F = function()
end

No, they aren’t the same.

local function F()

end

-- is the same as

local F
F= function()

end

This is so that the function is pre-defined when accessing it in its own scope. This allows you to call the function recursively. @Soflowsen

local F
F= function()
      F()
end

Note that the same goes for global functions.

You should always stick with local functions (they’re restricted to a specific scope depending on where they’re declared), the only use-case for a global function would be to access it anywhere in the script.

They are the same

I can write

local function F()
    print(1)
    F()
end
F()

And it runs without any errors (until the stack is full)

You are right, I have edited my post.

They can “shadow” them in their scope, but they cannot overwrite them

x = 5
local function F()
    local x = 2 -- doesn't overwrite the higher scope variable
    print("local x in F() " .. x)
end
F()

print("global " .. x)

local function G()
    x = 8 -- overwrites the higher scope variable
    print("global x in G() " .. x)
end
G()

print("global " .. x)
2 Likes

I never said they could overwrite higher-scoped variables, for the record.

Shadowing vs overwriting can cause some nasty, difficult to trace bugs if you aren’t super intentional with what you name your variables. (Commenting explicit overwrites and shadows is a useful technique for large code bases in my experience!)

1 Like

Why wouldn’t it prevent name collision, then? I thought name collision was unintentional overwriting.

I think you misunderstood.

local function f()

and

local f
f = function()

are the same, but

local f = function()

is not. In the case of the latter, f will be an undeclared variable from inside the function, resulting in an error if you run this.

local f = function()
    f()
end
f()
1 Like

Oh, I haven’t noticed that these were different. You are right; I said they are the same because OP was talking about local and global variables difference

1 Like

You can for example create decorators:

function decorator(func)
    local i = 0
    local function internal(...)
        i = i + 1
        print("function was called " .. i .. " times")
        return func(...)
    end
    return internal
end

function anyFunction(n)
    return n * n + n
end

function otherFunction(x)
    return x .. x
end

anyFunction = decorator(anyFunction)
otherFunction = decorator(otherFunction)

print(anyFunction(5))
print(anyFunction(2))
print(otherFunction("test"))

output:

function was called 1 times
30
function was called 2 times
6
function was called 1 times
testtest

It depends on intentionality. Intentionally shadowing a variable prevents name-collision bugs/errors, but accidentally doing so causes them.

It’s literally the traditional name collision problem in reverse: you’re NOT overwriting a higher-scoped variable when you WANT to, leading to bugs.

1 Like