Immutable vs Mutable

In HaasScript, tables are used to store collections of data and to represent objects. Tables are dynamic, which means that you can add, remove, and modify their elements at any time. However, there may be cases where you want to prevent the elements of a table from being modified. For example, you may want to create a table that represents a constant value and cannot be changed by mistake.

In this course, you will learn how to create read-only tables in HaasScript. A read-only table is a table that cannot be modified. Any attempt to add, remove, or modify its elements will result in an error.

Setting the __newindex metamethod

The __newindex metamethod is a special metamethod that is called whenever an attempt is made to add a new key/value pair to a table or to modify an existing key/value pair. You can use the __newindex metamethod to control how a table can be modified.

To create a read-only table, you can set the __newindex metamethod to a function that raises an error whenever an attempt is made to add or modify a key/value pair in the table.

Here is an example of how you can set the __newindex metamethod:

local t = {}

local mt = {
    __newindex = function(t, k, v)
        LogError("attempt to modify read-only table")
    end
}

setmetatable(t, mt)

In this example, mt is a table that has the __newindex metamethod set to a function that raises an error. The setmetatable function is used to set the metatable of t to mt.

Now, any attempt to add or modify a key/value pair in t will raise an error:

t[1] = 10 -- error: attempt to modify read-only table

Wrapping the table in a function

To make it easier to create read-only tables, you can wrap the table and its metatable in a function. The function takes the table as an argument and returns the read-only version of the table.

Here is an example of how you can wrap the table and its metatable in a function:

local function readonly(t)
    local mt = {
        __index = t,
        __newindex = function(t, k, v)
            LogError("attempt to modify read-only table")
        end
    }
    return setmetatable({}, mt)
end

The readonly function takes a table as an argument and returns a new table with the metatable mt set as its metatable. The __index metamethod is also set to the original table, so you can still access its values using the [] operator.

Creating a read-only table with the readonly function

To use the readonly function, you simply pass the table that you want to make read-only as an argument:

local t = readonly{1, 2, 3}

The readonly function returns a new table that is a read-only version of the original table. You can access the values of the read-only table using the [] operator:

Log(t[1]) -- prints 1

However, you cannot modify the values of the read-only table:

t[1] = 10 -- error: attempt to modify read-only table

Any attempt to add, remove, or modify the elements of the read-only table will raise an error.

Read-only nested tables

The readonly function only makes the top-level table read-only. If the original table contains nested tables, you can still modify the nested tables.

To make the nested tables read-only as well, you can use the readonly function recursively:

local function readonly(t)
    local mt = {
      __index = t,
      __newindex = function(t, k, v)
          LogError("attempt to modify read-only table")
      end,
      __pairs = function(t)
          return function(t, k)
              local v
              k, v = next(t, k)
              if k ~= nil then
                  if GetType(v) == ArrayDataType then
                      v = readonly(v)
                  end
              end
              return k, v
          end, t, nil
      end
    }
    return setmetatable({}, mt)
end

The __pairs metamethod is a special metamethod that is called whenever the pairs function is applied to a table. The __pairs metamethod returns an iterator function that allows you to iterate over the key/value pairs of the table.

In the updated readonly function, the __pairs metamethod returns an iterator that recursively applies the readonly function to any nested table that it encounters. This makes the nested tables read-only as well.

Now, when you create a read-only table with the updated readonly function, the nested tables are also made read-only:

local t = readonly{1, 2, {3, 4, 5}}

-- the following statement will raise an error
t[3][1] = 10

Conclusion

In this course, you learned how to create read-only tables in Lua by setting the __newindex metamethod to a function that raises an error whenever an attempt is made to add or modify a key/value pair in the table. You also learned how to use the __pairs metamethod to recursively make nested tables read-only.

By using read-only tables, you can prevent the values of a table from being modified by mistake and ensure the integrity of your data.

Back to: HaasScript Fundamentals > Variables