Mutability and immutability in Dart: Understanding var, const and final
Along years working with Dart and Flutter, one of the most repeated request change I made in pull-requests (PR) is to use final
and const
in the right place. This article tries to be a guide about how to use them.
Introduction
Most languages only use variables, a token that allows us to handle a set of bytes. Sometimes we do not modify the value in the memory, then why continue using variable
for classify them? That’s why many languages use constant
s.
But what about an object that is defined once but in runtime? For this case it exists a final
modifier.
Then, it is posible to propose a simplified definition for them:
- A
const
object is defined or calculated in compile-time and allows the compiler to make some optimizations in memory. - A
final
object can not be replaced with another object in memory, and its value is defined in runtime. - A
var
object can be replaced with another object in memory.
Immutability
A memory space is immutable if it is not possible to replace the the current object with another neither updating any attribute if it’s a composed object.
By definition, a const
object can not be mutated (i.e. can not be replaced with another object neither their fields can not be updated).
Here it is a questions, can a const
value can be assigned to a final
one?
The answer is yes, but it doesn’t make sense.
final obj = const MyClass();
obj
will be only a reference to the created const
value, then it can be only a const.
const obj = MyClass();
Mutability
With simple types, it is easy to understand how a memory space object is replaced with another one.
var x = 5;
x = 6; // this is possiblefinal y = 5;
y = 6; // syntax analyzer throws an error
But if it is an object, there is additional criteria:
var obj1 = MyClass(value: 2);
obj1.value = 3; // this is possiblefinal obj2 = MyClass(value: 4);
obj2.value = 5; // this is valid tooobj2 = ob1; // this is not valid because obj2 is a final objectclass MyClass {
MyClass({required this.value}); int value;
}
In addition, it is possible to assign a const
value to a variable.
var x = const MyClass(1);
x = const MyClass(2); // This is possible
Memory optimization with const
The superpower of const
is not visible if we use common types like int
or double
. To see the superpower, it is necessary to create a complex object.
The final
modifier, creates and object in a new memory space despite another final
object has value or fields with the same value.
But const
detects if another const
object contains the same value or fields with the same values, and instead of creating a new memory space, provides the reference the already created object. Thus, the compiler can optimize memory in compile-time.
That’s why you must procure use const
as much as possible.
Tricks to avoid var
Sometimes if is not possible to declare some object as final
because there is logic process to determine the final result.
It would be suitable to declare sum
as final
. Then, let’s explore some tricky solutions:
Using extension methods
For operations around an object with few others, extension methods can be a great solution.
Using self-executed-closure
When there are too many objects or there is no a main object, extension methods are not possible to use. But, an inline closure can help.
Using late final and scoped code
The closure alternative provides a good solution and can provide good tools to interrupt the code in any moment with return, but if it is not necessary, another solution is using `late final` (this is possible since null-safety feature in Dart) with scoped code.
Conclusion
We must use const
as much as possible to optimize. var
is necessary while the value is being edited, but it is better to have it as final
after the edition stage.
In addition, final
is not the same than immutable, but const
is.
There are more details to explore, but now you have a panoramic vision to understand what happens with your code.