tl;dr
static
makes a member (variable or method) available through the class instead of instances (object) of the class.final
modifies a variable, making it single-assignment variable. Meaning that the compiler expects exactly one assignment (no more, no less) to that variable.const
is a way to make a variable a compile-time constant. This means that the assigned value can neither change nor is it mutable.var
lets you define variables with a variable type. Variables being initiated with this keyword derive the type from the first assignment. After that an assignment with a value of a different type is not possible anymore.dynamic
is the most generic type. If you cast a variable todynamic
, the compiler will skip checking if the called method exists. Variables being initialized withdynamic
can change their type though assignments indefinitely.
Dart is a pretty verbose, statically typed language. It offers a rich variety of keywords to let you as a developer express precisely what you want.
Especially when it comes to variable assignments, there are multiple keywords that have a similar but different meaning. Let’s look at these keywords and examine what they do exactly.
The static
keyword
The static
keyword can turn variables into class variables and methods into class methods. What that means is that the variable or method belongs to the class rather than an instance of the class.
In other words: static
is only used for class-level variables and methods. It determines those which can be accessed without creating an object, just by referring to the class.
You can use it like this:
Unlike Java, you can not use static
to define a static class
or a static block
. Also, it’s not possible to use static
outside a class context.
Let’s look at what happens if we try to use it outside a class context nonetheless.
When we try to compile the above piece of code, the compiler responds with this:
Error: Can't have modifier 'static' here.
static int bla = 6;
^^^^^^
Be aware that static variables and methods violate the object-oriented paradigm. To be more specific, it goes against data being encapsulated in objects.
Static methods can not be inherited nor overridden.
Also, static variables reside in the memory as long as the program runs, whereas instance variables’ memory is freed once the object is garbage collected.
You should also avoid defining a class that contains only static members. You can use top-level functions instead.
Use cases
Although from a strict OOP perspective, using static
might often be a bad idea, there are cases, in which you will barely get around using it.
One of those cases is the usage of isolates.
Isolates are a way to implement actual concurrency in Dart. Everything inside Dart’s VM runs in an isolate. Every isolate has a separate event loop and an isolated memory heap.
If you have heavy computational code that would otherwise put a big load on the main thread, you can use isolates to prevent this from happening.
The function passed to the spawn()
function, which creates a new isolate, needs to be a top-level function (meaning that it’s outside a class) or a static method. If you want the function to be part of a class, you won’t get around making it static.
The final
keyword
final
is a keyword that has the semantic meaning of a single assignment. It has to be assigned exactly once (if it’s used).
If we define a final
variable, which we do not assign, the compiler will complain if there’s a usage (in the following example a print()
statement):
The consequence is a compiler error:
Error: Final variable 'myNumber' must be assigned before it can be used.
print(myNumber);
If we define a final
variable, which we assign more than once, the compiler will complain as well:
This code block leads to the following compiler error:
Error: Can't assign to the final variable 'myNumber'.
myNumber = 3;
^^^^^^^^
Error: Compilation failed.
It doesn’t matter if the assignment happens immediately during initialization, so this would have the same effect:
The idea behind final
variables is that the compiler prevents you from situations of an accidental value change of a variable although you already you know the value is not supposed to change.
It ’s a good practice to make every variable final
at first. When you realize, you need to change its value during runtime, you can remove the keywords afterwards. It’s something the official linter would also complain about.
final
. However, this does not make the object immutable!Let’s consider the following example:
If you don’t specify a type like in the above code example, the compiler makes the variable the type of the first assignment (List<int>
in this case).
While the execution of the add()
method on the List is perfectly fine because the value of myNumbers
is not immutable, the assignment in line 5 is problematic as final
prevents further assignments after the initialization.
The execution of the code results in the following compiler error:
Error: Can't assign to the final variable 'myNumbers'.
myNumbers = 'a different type';
^^^^^^^^^
Error: Compilation failed.
The const
keyword
With const
, you can tell the compiler, that the variable value you’re about to create, is supposed to be immutable and thus can be generated at compile-time.
Let’s look at an example to make things clearer:
The execution of the above code results in an error:
Unhandled Exception: Unsupported operation: Cannot add to an unmodifiable list
It’s important to notice that this error comes up during runtime and not compile-time, which is a little bit confusing because it’s foreseeable at compile-time that this will fail. This issue is discussed on GitHub as well
Under the hood, the const
keyword leads to the List
becoming an UnmodifiableListMixin
(dart-sdk/lib/internal/list.dart
). In this abstract class, which implements List
, every modifying method is overridden and throws an UnsupportedError
.
Here is an example of the overridden add()
method:
1abstract class UnmodifiableListMixin<E> implements List<E> {
2 …
3 /** This operation is not supported by an unmodifiable list. */
4 void add(E value) {
5 throw new UnsupportedError("Cannot add to an unmodifiable list");
6 }
7 …
8}
In the case of nested state like lists that contain lists or maps, the nested level is also immutable.
Modifying nested values of a const
variable value leads to the same error:
Unhandled Exception: Unsupported operation: Cannot add to an unmodifiable list
This is expected as the whole state of the variable value is being generated during compile-time.
The official documentation states:
"An unmodifiable list cannot have its length or elements changed. If the elements are themselves immutable, then the resulting list is also immutable."
The var
and dynamic
keyword
Although Dart is a statically typed language, we have the possibility to define a variable with a dynamic type. The keyword used for this is called var
. If you know Javascript, you might know this keyword.
Defining a variable using the var
keyword, causes two effects:
- The variable has an initially undefined type (
dynamic
) - Once a value is assigned to the variable, it gets the type of the value, which can not be changed afterwards
This leads to the following error during runtime.
Error: A value of type 'String' can't be assigned to a variable of type 'int'.
myVar = 'seven';
^
Error: Compilation failed.
By casting objects to dynamic
, you can call every method without the compiler complaining.
This is not the case for var
as var
is not a type.
1(myObject as dynamic).whatever(); // This is a valid cast and the compiler will not complain
2(myObject as var).whatever(); // This is invalid as var is not a type
If you use dynamic
as the type during variable initialization instead of var
, the behavior is similar but different.
The key difference is that a variable that is initialized using dynamic
can still change its type after its first assignment whereas var
forbids this.
1dynamic myNumber = 1; // myNumber is of type int.
2myNumber = 2; // The assignment changes the value of myNumber from 1 to 2.
3myNumber = 'a'; // The assignment changes the value oof myNumber from 2 to 'a'.
4
5var myNumber = 1; // myNumber is of type int.
6myNumber = 2; // The assignment changes the value of myNumber from 1 to 2.
7myNumber = 'a'; // Error: A value of type 'String' can't be assigned to a variable of type 'int'.
Conclusion
In order to master a programming language, the developer needs to know the exact meaning of the main keywords and when it’s time to use them.
Because Dart has a rather sophisticated type system, there are quite a few keywords that revolve around this topic.
I recommend to always use static typing. This way you can avoid using var
and dynamic
and all the issues associated with these keywords completely.
final
should be used by default for every newly defined variable as it protects the developer from unwanted reassignments of variables.
const
should be used when the value is known during compile-time and never changes. Also a good choice for widgets with a static content.
Comment this 🤌