Question

Is there a way to use <const> inside a Lua table?

Since Lua 5.4, the <const> syntax lets us set const variables. I noticed that this doesn't transitively affect fields inside a table.

local x <const> = {
    a = {1,2,3},
    b = {5,6,7}
}

x = 5 -- error
x.a = 9 -- no error

The table x is const and cannot be reassigned, but fields inside the table can.

Is there any way to make the fields inside a table also const via syntax alone? I know that it's possible via the index and newindex metamethods, but I'm curious if it's possible with simple syntax like <const>.

I tried the following, but it produces a syntax error:


local x <const> = {
    a <const> = {1,2,3},
    b <const> = {5,6,7}
}
 4  86  4
1 Jan 1970

Solution

 3

const is used to declare a local variable constant, but tables are not variables: they are objects (or values in Lua parlance). There are no immutable tables in Lua. In the OP code x is a constant variable initialized to hold a table as its value; no further assignments can be made to x. The value which x holds is a table, and that object is mutable.

It is not possible in Lua to use const in the manner described in the OP question. In the posted code x is a (constant) variable to which is bound a table. In that table, a and b are not variables, but they are keys. In Lua a key (or an associative array index) is another value. But remember: values are not variables, rather variables store values. It may help to recall that x.a is just syntactic sugar for x["a"], i.e., the table x has a slot indexed by the value "a". Lua allows the const attribute to be associated with variables, but not values, so a and b cannot be made const.

There may be some ways to simulate immutable tables using metatable magic, but I suspect that these would come with performance penalties which would defeat one of the reasons for using immutable objects in the first place. I would avoid this sort of thing. There may be some instances where it would be convenient to have immutable tables built into Lua, but if you really think that you need this you should probably consider whether another language is more appropriate for your problem.

2024-07-12
ad absurdum

Solution

 1

Here's a slightly different perspective. (The answer is still no.)

Part of the reason that <const> works like it does is because (at least in the standard Lua implementation) it is evaluated purely at compile time. This is very simple to do -- local variables are lexically scoped, and the compiler needs to know about the variable declaration to do anything anyway -- for example it needs to know that it is in fact a local, and whether or not it is an upvalue.

The VM runtime knows nothing about <const>. The only difference is if the compiler decided to use the information to make some optimization.

This paradigm doesn't work so well for a theoretical const table feature. We can ask if it is reasonable to expect the compiler to be able to determine whether a table is const (and if so, which fields, or whatever depending on how you implement this feature). In many cases this may be possible through complicated static analysis, but such analysis is probably much more complicated that anything the Lua compiler is currently doing, and you may start running into Halting Problem kinds of issues in more complex edge cases.

Or, you might invent a way to provide additional information to the compiler that helps it resolve this question. But, if the compiler easily knows which value is a const table, and in what way it is const, I think you have just reinvented strong typing. People have given Lua stronger typing before. It may be an interesting discussion, but it is somewhat of a philosophical departure.

If you don't do it at compile time, I guess you do it at runtime. This means, you would have some tag or metadata carried with the table object in memory and the VM would check this at runtime. As you know Lua has a flexible user-accessible system that allows this kind of feature -- metatables. Yes, it is true that your suggested syntax may be pretty convenient for your exact use case, but maybe it is not good to use the same syntax for two features that actually work quite differently from each other. (Imagine the question "Why did <const> fail at compile/load time here, but crash at runtime there?") And I think creative use of the existing features and defining appropriate helper functions may be able to achieve some kind of compromise between syntax convenience, runtime cost, and readability.

2024-07-13
tehtmi