Introduction
A little while ago I came across this tutorial explaining getfenv()
and setfenv()
. So why am I making this tutorial when that one already exists? Well they did a pretty awful job explaining and following that tutorial is bad practice.
Function Environments
Function environments are environments where your code runs. At their most basic/tangible form they are tables. Let’s see an example of a script:
local a = 5
b = 3
The environment of this script defines b
as 3
since the variable is global. Since a
is local and not global if you attempt to print the contents of this environment you’ll see it isn’t visible.
Function environments work using a sort of layer-system. Let’s say we had the following code:
-- Layer 2 relative to b
x = 99
function a() -- Layer 1 relative to b
function b() -- Layer 0 relative to b (itself)
end
end
So if you want to go out of a deeply nested function you simply start at 0, the starting environment, and work your way out. This will be better explained when we look at interacting with environments.
Interacting with Environments
Lua provides two useful functions for interacting with function environments. They are getfenv()
and setfenv()
.
getfenv()
You can get a function environment using this function. Let’s say we wanted to print a function environment:
local a = 5
b = 3
print(getfenv(0))
Just as explained above b
is accessible but a
is not. You’ll also notice script
. In Roblox when you attempt to reference a script’s instance you use the script
keyword. Function environments are how Roblox makes the keyword accessible.
Environments also contain a metatable. However, these are locked and not accessible in Roblox:
print(getmetatable(getfenv(0))) --> Outputs: The metatable is locked
That said, you can still alter the metatables of environments. One potential use case for this is the creation of “custom” enums. In this example I’ll just make it so if you attempt to print the Enum
global it prints Hello World!
:
local originalEnum = getfenv(0).Enum -- Store original Roblox-defined enums
getfenv(0).Enum = setmetatable({}, {
__tostring = function()
return 'Hello World!'
end,
__index = function(_, k) -- Preserve Roblox-defined enums
return originalEnum[k]
end
})
print(Enum) --> Outputs: Hello World!
Note: This is happening within the script’s function environment alone. What this means is other scripts will maintain their original environments.
setfenv()
You can set a function environment using this function. Let’s say we wanted to override the existing function environment:
y = 30
setfenv(0, { y = 2 })
print(getfenv(0)) --> Outputs: { ["y"] = 2 }
That’s pretty much all there is to it. Keep in mind using setfenv()
overrides the entirety of an environment. This is where referencing a value within getfenv()
can be useful. E.g.
u = 45
getfenv(0).SomeValue = 15
print(getfenv(0)) --> Outputs: { ["SomeValue"] = 15, ["u"] = 45 }
Notice how the variable u
is preserved.
Modules & Environments
When you require()
a module the module executes code in its own environment. However, the value it returns becomes a part of the environment that require()
ed it. So for example,
-- Script
local x = require(script.ModuleScript)
x.DoStuff()
print(abc) --> Outputs: Hey!
-- ModuleScript
local module = {}
function module.DoStuff()
getfenv(2).abc = 'Hey!' -- 2 is the layer the original script is under, 1 is the module script, 0 is this function
end
return module
Conclusion
I hope this tutorial helped explain function environments and you now understand how powerful interacting with these environments can be. These are majorly overlooked when scripters start learning Lua when they should be one of the first concepts learned.
Warning: Interacting with function environments in Roblox will disable Luau’s built-in optimizations due to it being considered untrusted code as seen here.