Dart: “static”, “const”, and “final”

With the droves of developers flocking to Dart/Flutter as a cross-platform development solution, I tend to see the same questions come up again and again on Slack, Stack Overflow, and Discord as well as many other forums. One such question is What is the difference between “static”, “const”, and “final”? For the uninitiated, they can seem similar.

Static Variables and Methods

Bob Nystrom wrote a nice post on this topic a few years back and it was then re-posted by Seth Ladd. If you’re new to Dart/Flutter you may not know these gentlemen. Bob is one of the Dart engineers at Google and Seth Ladd is the Product Manager for Flutter. So you really want to listen to these guys when it comes to Dart and Flutter.

Bob stated in his post that “static”, “final” and “const” mean entirely distinct things in Dart:

  • “static” means a member is available on the class itself instead of on the instance of the class. That’s all it means, and it isn’t used for anything else. “static” modifies *members*.
  • “final” means single-assignment: a final variable’s value cannot be changed. “final” modifies the *variable*.
  • “const” has a meaning that’s a bit more complex and subtle in Dart. “const” modifies *values*. You can use it when creating collections, like const[1,2,3], and when constructing objects (instead of new) like const Point(2, 3). Here, const means that the object’s entire deep state can be determined entirely at compile-time and that the object will be frozen and completely immutable.

“const” objects have some interesting properties:

  1. Their value must be available at compile time.
  2. They are deeply, transitively immutable. For example, const collections must only contain “const” values. Comparatively, final collections may have their values modified at runtime.
  3. They are *canonicalized*. For any given const value, a single object is created and re-used, no matter how many times the expression(s) are evaluated.

So what does all this mean for you and your code? Well, let’s take a closer look with some example code.

First, let’s tackle “static”. If you try and define a static variable anywhere in your code other than a class, you will receive an error telling you that you cannot use “static” here. This is because static is used to create the variable on the object. If there is no object, there is no place to bind the variable. So code like the following will not work:

static int myStaticVar = 0; <<< Error: Can't have static modifier here.

void main() {
  myStaticVar++;
  print('Value: $myStaticVar');
}

You can move the myStaticVar declaration into main and you’ll get the same error because there will still be no class to bind the variable to.

Now, if you create a class and declare a static variable inside the class, the code will work:

class MyClass {
  static int myStaticVar = 0;

  get count => myStaticVar;
  int inc() => myStaticVar++;
}

void main() {
  var myInstance = MyClass();

  for (int i = 0; i < 10; i++) {
    myInstance.inc();
    print('Value: ${myInstance.count}');
  }
}

So what is so different about this? I can do this without static! You might say, and you would be correct. What you can’t see here is that myStaticVar is bound to the class and not the object. Let me demonstrate this:

class MyClass {
  static int myStaticVar = 0;

  get count => myStaticVar;
  int inc() => myStaticVar++;
}

void main() {
  var myInstance = MyClass();
  var mySecondInstance = MyClass();

  print('Compared Objects are exact match: ${myInstance == mySecondInstance}\n\n');
  for (int i = 0; i < 10; i++) {
    myInstance.inc();
    print('Value A: ${myInstance.count}, Value B: ${mySecondInstance.count}');
  }
}

Here I’ve created two instances of MyClass. Next, a comparison is done to ensure that the two instances are indeed two unique instances. Next, We enter the loop and print the value of myStaticVar from each instance. You’ll notice that the value is the same for each instance even though the second instance was never incremented. The two instances share the same myStaticVar member. The output from the above code is shown below:

flutter: Compared Objects are exact match: false
flutter: Value A: 1, Value B: 1
flutter: Value A: 2, Value B: 2
flutter: Value A: 3, Value B: 3
flutter: Value A: 4, Value B: 4
flutter: Value A: 5, Value B: 5
flutter: Value A: 6, Value B: 6
flutter: Value A: 7, Value B: 7
flutter: Value A: 8, Value B: 8

There is one more aspect of static members to consider. Since the static variable is bound to the class and not the object instance, you don’t even need an object instance to use the variable.

class MyClass {
  static int myStaticVar = 0;
}

void main() {
  for (int i = 0; i < 10; i++) {
    MyClass.myStaticVar++;
    print('Value A: ${MyClass.myStaticVar}');
  }
}

If you run the code above you will get the expected output. Notice, however, that here we use the variable directly. In our earlier code, we used methods to access myStaticVar. This worked as the compiler could guarantee that myStaticVar would exist. The method count and inc, however, are instance methods. They are bound to an instance object of the class. Not the class itself as myStaticVar is. So if we tried to use these methods here we would get compile-time errors.

The interesting thing here is that Dart, allows us to have static methods! As with member variables, static methods will be bound to the class and not the object instance. This allows them to be called on the class without needing an object instance. Let’s see this in action:

class MyClass {
  static int _myStaticVar = 0;

  static get count => _myStaticVar;
  static void inc() => _myStaticVar++;
}

void main() {
  for (int i = 0; i < 10; i++) {
    MyClass.inc();
    print('Value A: ${MyClass.count}');
  }
}

Here I made _myStaticVar private so it can’t be used directly in main. This forces the use of int and count to set and get the value of myStaticVar. Also note that these two methods are now defined as static methods. If we didn’t do this the methods would be bound to the instance and not the class. As you can see we used these methods directly from the class without needing to create an instance of the class.

So when should you use a static method or variable? Typically, you would use a static member variable when the value stored is independent of the object instance. Use static methods when the code in the method does not depend on instance creation and is using only static variables, or when the method should not be changed or overridden. Another use is when you want to use a method of the class without instantiating an object of the class.

Final

I am sure you have seen the use of “final” in almost every Dart project. It is quite common. So what does it do?

“final” is used to ensure that a variable can only be assigned once. If you declare a field (member variable) final, it must have an initializer, and once a value is assigned it cannot be reassigned. The final keyword works on the variable modifying it so that it’s value can never change.

Const

OK hopefully, you now understand the static and final keywords. Next, we are going to talk about the “const” keyword and what it does.

“const” is a bit more complex and subtle. The “const” keyword also modifies values. It changes the value so that its entire “deep state” can never change. This means that the compiler must be able to determine the object’s deep state at compile time. const effectively freezes the object making it completely immutable.

Let’s look at some sample code. The following example show some valid const initializations:

const numTakes = 5;
const maxSensors = 3 * numTanks;

These are not:

const recordingStartDate = DateTime.now();
const now = DateTime.now();
const recordingElapsedTime = now.subtract(recordingStartTime);

All lines of code above will be met with the error message “Const variables must be initialized with a constant value“. This is because the value of DateTime.now() can only be known at runtime. So the top two lines are trying to initialize the variable with an unknown value. The third line has two issues. First, it is using a method on an object that doesn’t exist at compile-time, so subtract can’t be called. The second issue is that it is still using an unknown value “recordingStartTime”. So even if the subtract method was available, it still couldn’t do the calculation.

const vs. final

Now let’s look at the difference between const and final using a list as our example:

const myConstList = <int>[1, 2, 3, 4, 5];
final myFinalList = <int>[1, 2, 3, 4, 5];

void main() {
  myFinalList[0] = 0;
  myConstList[0] = 0;
  print('myFinalList: $myFinalList');
  print('myConstList: $myConstList');
}

If you try and run this code you’ll get an error: “Unsupported operation: Cannot modify an unmodifiable list“. Now comment out the line that changes index 0 of the myConstList and run the code again:

const myConstList = <int>[1, 2, 3, 4, 5];
final myFinalList = <int>[1, 2, 3, 4, 5];

void main() {
  myFinalList[0] = 0;
  //myConstList[0] = 0;
  print('myFinalList: $myFinalList');
  print('myConstList: $myConstList');
}

Now you’ll see the code runs and you can still change the values in the myFinalList. So what effect does final have in our code if we can still change the values of the list? Well, if we try and reassign myFinalList to a new list, we’ll get an error. Try the following code:

const myConstList = <int>[1, 2, 3, 4, 5];
final myFinalList = <int>[1, 2, 3, 4, 5];

void main() {
  myFinalList[0] = 0;
  myConstList[0] = 0;
  print('myFinalList: $myFinalList');
  print('myConstList: $myConstList');
  myFinalList = <int>[6, 7, 8, 9]; <<< Error: myFinalList can't be used as a setter because it's final.
}

So using final, the list object can’t be changed. However, the values inside the list can be!

So when should you use const and final? Use const anytime the value is constant and can be calculated at compile time. Doing so can increase the performance of your code and reduce it’s size. Use final when you want to assign a value to a variable and have it remain that value for the life of your program.

I hope this has shed some light on the topic of static, const, and final.

Until next time, Happy Coding!

Leave a Reply

Your email address will not be published.