How can I make this code work with Roblox Lua?

I made a code snippet that makes all global variables immutable, with an optional tag-like thing in the variable name (“m_”) which functions similarly to Rust’s mut keyword, or the opposite of const (where things are const by default).

The goal was to make it so that all variables that are redefining create an error saying that the variable cannot be changed. However, the method I used (seeing if they’re added to the global table) doesn’t work in Roblox because Roblox’s _G behaves differently.

Here’s the code:

local function immut(item)
    return setmetatable({}, {
        __index = function(_, key)
            return item[key]
        end,

        __newindex = function()
            error("Attempt to redefine immutable object " .. tostring(item))
        end,
    })
end

setmetatable(_G, {
    __index = function(_, key)
        return rawget(_G, key)
    end,

    __newindex = function(_, key, value)
        if tostring(key):sub(1, 2) == "m_" then
            rawset(_G, key, value)
        else
            rawset(_G, key, immut(value))
        end
    end,
})

thing = {ok = "test1"}
print(thing.ok) --> test1
pcall(function()
    thing.ok = "hm" -- will error because it's being redefined
end)

m_thing = {ok = "yes"} -- m_ is for mutable
print(m_thing.ok) --> yes
m_thing.ok = "no" -- won't error, because it has "m_"

local thing2 = {yes = "ok"}
print(thing2.yes) --> ok
thing2.yes = "no" -- won't error, it's a local variable so it won't get added to _G.

Basically, in Roblox, when you define a variable both with and without local it doesn’t get added to the global table (actually with the new script analysis I’m pretty sure it warns you when you define a variable without local).

But in the code, I rely on that functionality to make every variable automatically immutable, so I want to know how I refactor my code so it doesn’t rely on that.

2 Likes

Nice, I wouldn’t have thought of this.

I don’t believe you can do this in Roblox. My first idea was to use getfenv to get the calling environment of the script. I came up with this implementation:

local DefinedMutability = {};

DefinedMutability.mutablePrefix = "m_"

local function __index(callingEnv, key)
    return rawget(callingEnv, key);
end

local function __newindex(callingEnv, key, value)
    local startIndex, endIndex = key:find(DefinedMutability.mutablePrefix);

    if startIndex == 1 then
        if key:len() ~= endIndex then
            rawset(callingEnv, key, value);
        else
            local message = string.format(
                "Cannot declare a variable by the name of %q",
                key
            );
            error(message, 2);
        end
    end

    local message = string.format(
        "Cannot assign to %q because it is immutable."
        .. "To make a global mutable, prefix it with %q",
        key,
        DefinedMutability.mutablePrefix
    );
    error(message, 2);
end

function DefinedMutability.apply()
    local callingEnv = getfenv(1);

    setmetatable(callingEnv, {
        __index = __index,
        __newindex = __newindex,
    });
end

return DefinedMutability;

This didn’t work, as I was reminded of the __metatable field. Although the main issue is that the metatables for those environments are locked, therefore you can’t modify the metatable through “setmetatable”. I believe your best bet to “implement” this would not be at runtime, but through some static checker.

I’d also suggest some static checker because magic code like this can become very troublesome.

1 Like

Calling getfenv or setfenv in your scripts disables a handful of good Luau optimizations. This should also be taken into account before going on to mess with the Lua environment.

1 Like

yea. Also i’m pretty sure that these globals were removed entirely in newer versions of lua and replaced with __ENV.

I’ve recategorised this thread over to Scripting Support. Please remember that Code Review is for improvements of already working code, while Scripting Support is for resolving programming issues. More information is available in our category guidelines.

1 Like