Unable to use table.freeze on a table with a locked metatable

I’m not sure if this fully qualifies as a bug, as this behavior was said to be somewhat intentional in this Luau GitHub issue from 2022, but I thought it’d be interesting to see if maybe this is something worth revisiting.

I’m sure that this issue is very niche and only affects very few people — probably just me — so it’s not exactly important. It just would be nice quality of life if this was allowed.


Anyway, on with the actual bug report…

In one of my projects, I have a prototype-based object system. You call something like:

local prototype = require("path/to/prototype")
local obj = Object.create(prototype, {
	veryCoolState = true
})

…and it returns a new ‘object’ of prototype with the passed state.

more irrelevant info on how my object system works

If prototype contained methods like sayHello() and doACoolFlip() then the returned object obj would also contain those methods.

State is the second parameter passed into the Object.create function, it just applies the fields to the object.
So if you passed a state like:

{
	isCool = true,
	isAwesome = false,
}

…then indexing obj.isCool and obj.isAwesome would return true and false respectively.
This is useful for if you want to set some properties of an object before it gets fully initialized.

Initialization is basically just if a prototype has a New() method, then it is called before returning to the user.

Prototypes are created using a constructor function called Prototype (real creative, i know), which just takes in an info table that contains fields/methods it should contain. It’s also responsible for “compiling” (really need to come up with a better term) any components the prototype may use.

I was going to have a whole example on how this works, but it’s really not important to the bug report so I’ll refrain for now…


Now this works fine on it’s own, but I want the Prototype objects to be completely immutable. Purely read only, just so that you don’t end up accidently modifying the Prototype when you meant to modify an object.
I’m using the __metatable metamethod as a sort of type indicator, which you can retrieve with getmetatable, but you can’t do that with table.freeze.

I suppose that having a type field would also work just fine, but it’s not as elegant. I also know there’s other ways to implement table.freeze using purely metamethods and proxies, but it feels kind of redundant when we have a perfectly fine built-in function that already does that.


Another thing that bugs me is that the documentation for table.freeze and the __metatable metamethod don’t mention anything about how they aren’t compatible with eachother. This seems like a nitpick, as this issue is already super edge-casey, but I feel that it might be worth adding even a small note about how they don’t work together.

1 Like

Another annoying thing that I found out earlier today is table.clone also throws an error when it’s passed a table with a locked metatable. I don’t really get why it’s this way, I thought that table.clone just returned a shallow copy without modifying anything about the original table, let alone the metatable.

This can easily be reproduced by executing this code:

local lockedMetatable = { __metatable = "locked" }
local tableWithALockedMetatable = setmetatable({ imACoolKey = "with a cool value" }, lockedMetatable)

table.clone(tableWithALockedMetatable) -- invalid argument #1 to 'clone' (table has a protected metatable)

I don’t understand why this is the case, all this does is force me to manually clone all of the key/values into a new table with a for loop which is just more unneeded redundancy.

This error doesn’t happen when calling any other table function (except freeze) on a table with a locked metatable.

It’d be nice if someone could give me a reason for this type of behavior, as it makes working with tables with locked metatables really annoying.

1 Like

This behavior is intentional and is not a bug.
We do not have plans to change this at this time.

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