The word mutable comes from the Latin word mutabilis, which means “liable to change”.
What is mutability?
Mutability in programming refers to the capability of being changed or modified after creation/declaration.
There are two kinds of mutability which are relevant to us:
- Mutability of pointers
- Mutability of values
Mutability of pointers
In Luau we usually use the local keyword to define a variable. This creates a mutable pointer.
This is a bit simplified but, a pointer is just something that references a value, in this case myVar ‘points’ to “Hello World”.
Now what exactly does it mean for a pointer to be mutable? Well it means that the pointer can be made to point at a different value:
local myVar = "Hello World"
myVar = "Goodbye World" -- myVar now points to "Goodbye World"
Here is a visual representation of what happens.
At first myVar is assigned:
Then, it is reassigned so it points to a different value:
The garbage collector will clean up the
"Hello World"string left in memory
For a long time in luau, we were limited exclusively to only mutable pointers, however now we have immutable (constant) pointers which can be defined using the const keyword.
Immutable pointers cannot be reassigned, they will always point to the same value:
const myConst = "Hello World"
myConst = "Goodbye World" -- this will throw an error
Constants are not heavily used in Luau as they are relatively new and also because the only purpose they serve is to catch accidental reassignment, which is not a mistake that happens often.
They may become more popular in the future.
Mutability of values
Mutability of values is the more important form of mutability for luau/roblox programmers.
Certain datatypes in Luau are mutable while others are immutable.
Immutable datatypes:
nilbooleannumberstringfunctionvector
Mutable datatypes:
userdatatablebufferthread
The Roblox also introduces it’s own engine datatypes:
- Mutable:
Instance,RaycastParams,OverlapParams,TweenInfoetc. - Immutable:
Vector3,Vector2,CFrame,Color3etc.
When a value is immutable, that means that any function or operator acting on it, cannot modify the original value. Instead a new value is created.
In the case of mutable values, the orginal may or may not be modified.
Let’s see some examples.
Immutable example
The following code creates a string and then uses the string.lower function on it, then prints both values:
local message = "Hello World"
local newMessage = string.lower(newMessage)
print(message)
print(newMessage)
Output:
Hello World
hello world
Here’s what actually happens. First the value "Hello World" is created in memory, then it is assigned to the message variable:
After that, the
string.lower function is called, passing message as an argument, this creates a new string "hello world" which is assigned to newMessage:Mutable example
These are some functions that do the same thing but one mutates the value passed in and one doesn’t:
local function list_upper_mut(list)
for i, v in list do
list[i] = string.upper(v)
end
return list
end
local function list_upper_nomut(list)
local newList = {}
for i, v in list do
newList[i] = string.upper(v)
end
return newList
end
Let’s see what happens when we use them. First the mutating one:
local list = {"a", "b", "c"}
local newList = list_upper_mut(list)
print(list)
print(newList)
Output:
{
"A",
"B",
"C"
}
{
"A",
"B",
"C"
}
Now, what is actually happening is, at first list is initialised:
Then the function mutates the original value and
newList is assigned to the same value:Now let’s try the non-mutating function:
local list = {"a", "b", "c"}
local newList = list_upper_nomut(list)
print(list)
print(newList)
Output:
{
"a",
"b",
"c"
}
{
"A",
"B",
"C"
}
list is also initialized here like the previous case:
The difference however is that instead of mutating the original, the function creates a new table and assigns it to
newList:Why does this matter?
Imagine this, somewhere in your code you are working with a table, perhaps an inventory table which contains items.
You have a function that is supposed to take the inventory as a parameter and return a list of items of a specific rarity.
However, when writing this function, instead of creating a new table, you mutated the original table (oops!).
What happens now? All the items except the rarity you chose, disappeared from the inventory table! :c
What can i do to avoid this?
Here are some pieces of advice that can prevent bugs caused by mutability/immutability:
- Check whether a function mutates it’s arguments or not. This goes for roblox API as well.
- If a function does not mutate data but instead creates a modified copy, make sure to return the modified copy (this is pretty obvious, if you don’t return it you will only be stuck with the original which hasn’t been modifed)
- If a function mutates an some data, do not return that data from that function (returning the data would make it seem like the function creates a new copy). An exception to this is methods which are meant to be chained. i.e.
-- Okay
local list = {...}
local newList = nomut_function(list)
-- Okay
local list = {...}
mut_function(list)
-- Not good
local list = {...}
local newList = mut_function(list)
-- Okay
local object = Class.new()
object
:method1()
:method2()
- if a function you write operates on a mutable data type and it is not immediately obvious whether it mutates the object or not, consider mentioning it in the documentation:
--[[
Blah Blah Blah...
Params:
- list: blah blah... (mut)
- lis2: blah blah... (no_mut)
]]
local function myFunc(list, list2)
...
end
Summary
Here are the main takeaways:
- Mutable values may or may - Mutable pointers (variables) can be reassigned, immutable pointers (constants) cannot.
- Immutable values cannot be changed. A copy must be made.
- Be aware of whether your code mutates mutable values or not, not doing so can lead to bugs.
- Make sure it is clear whether a function is mutable or immutable.
p.s. if you find any issues with this post, or have any suggestions, feel free to contact me on my discord (@rancidmilk1.5)








