
Build 10x products in minutes by chatting with AI - beyond just a prototype.
Like many programming languages, Dart allows you to store data using variables. The value of a variable can typically be changed after it has been initialized, making it mutable. However, as a developer aiming to write efficient and maintainable code, you often need a way to ensure that the values remain constant, which is where Dart's final and const keywords play a crucial role.
final and const KeywordsWhen you declare a variable in Dart, you might decide its value should never change. This is when you reach for the final and const keywords, which can be used to create immutable values. But while these two keywords might seem similar at first glance, they serve different purposes, and understanding the main difference between final and const is crucial in writing efficient and maintainable code.
With the final keyword, you're telling the Dart compiler that a variable can only be assigned once and its value is determined at runtime. For example:
void main() { final String greeting = 'Hello, world!'; print(greeting); // Outputs: Hello, world! }
In this simple example, the final variable greeting is assigned a runtime value, a string, and cannot be changed later on.
As for the const keyword, it takes immutability a step further. A const variable has to be a compile-time constant, which means its value should be known at compile time. This is not only about writing maintainable code but also can improve performance since these values are determined when your code is compiled, not as it’s running. Here's how it looks in Dart:
void main() { const String exclamation = 'Welcome to Dart!'; print(exclamation); // Outputs: Welcome to Dart! }
The const keyword ensures that the value assigned to the exclamation variable is a compile-time constant—it can't be changed or assigned a new value after compilation.
final: A Closer LookWhen working with Dart, the final keyword is your ally in creating variables that need to be assigned once and only once. A final variable's value can be determined at runtime, which offers excellent flexibility. This allows final variables to be initialized with data that is not known at compile time, lending itself to dynamic and responsive applications.
finalThe final keyword differs from const because it allows for lazy initialization. This means you can assign a runtime value to a final variable—a value determined when the code is run. For example:
void main() { final int userAge = getUserAge(); // Assume getUserAge() retrieves the user's age at runtime. print(userAge); // The actual value is determined at runtime and can't be known at compile time. }
In this scenario, the final variable userAge holds a value that is assigned once at runtime and cannot be reinstantiated with a new value later in the code. Using a final keyword instead of const is crucial when the value to be assigned cannot be established at compile time.
final in Class-Level VariablesAt the class level, the final keyword helps create immutable instance variables that are initialized at the constructor level and are unique for each class instance. This aspect of final variables plays a significant role in object-oriented programming and in creating immutable objects in Dart:
In the above class , and are variables. Once the object is created, these properties cannot be changed. Even if a new is instantiated, it will have its unique properties, providing tailored immutability at the instance level.
Going beyond the keyword, Dart elevates immutability to a new level with the keyword. When you declare a variable, you're saying the value won't change and ensuring the variable is a compile-time constant. This means its value is determined during the compilation phase and baked into the code, which can lead to significant performance improvements.
variables in Dart are defined to be compile-time constants. The keyword demands that the variable's initial value be known at compile time; thus, assigning a runtime value to a variable is impossible. This restriction allows the Dart compiler to optimize the performance by embedding constant values directly into the code. Here's how you might use in a Dart program:
In the example above, is a compile-time constant known at compile time and will never change. The Dart compiler understands this and can optimize accordingly.
Dart also allows you to define literals as . This is particularly useful when dealing with objects like lists or maps where you want the entire data structure to be immutable:
In the list , not only is the list itself a constant but each element is a compile-time constant too, illustrating how ensures deep and comprehensive immutability.
Deep immutability with goes beyond just the immediate value of a variable. Ensuring that an object is transitively immutable—meaning that all of its fields and their values are also immutable—is another powerful aspect of the keyword:
With the keyword applied to , you guarantee that once an object like is created, it is completely immutable, including its properties and .
The decision between and can have implications for the performance of your Dart application. The performance benefit of using variables comes from their nature as compile-time constants. Since variables are known at compile time, they are evaluated once during compilation. This means the Dart compiler can optimize them away, saving memory and reducing startup time.
On the other hand, variables are evaluated at runtime. They ensure a variable holds a single value after initialization, but this comes with the added benefit of allowing the assignment of values only known at runtime.
The main difference between and can be summarized: is about ensuring a single value post-initialization, potentially assigned at runtime, while enforces immutability and constant values known at compile time.
Mastering the use of and in Dart empowers developers to write robust and efficient code. By judiciously applying and , you can avoid mutable state issues, streamline your codebase, and ensure that your Dart applications are built on a foundation of well-defined, immutable data.
UserProfilenameaccountCreationDatefinalUserProfileUserProfilefinalconstfinalconstconstconstconstconstconstconstbirthYearconstconstconstfibonacciNumbersconstconstconstconstconstImmutablePointImmutablePointoriginxyfinalconstfinalconstconstconstfinalfinalconstfinalconstfinalconstfinalconstfinalconstfinalconstclass UserProfile {
final String name;
final DateTime accountCreationDate;
UserProfile(this.name, this.accountCreationDate);
}
void main() {
final user = UserProfile('Alice', DateTime.now());
print(user.name); // Outputs: Alice
// user.name = 'Bob'; // This would result in a compile-time error.
}void main() {
const int birthYear = 1990; // Known at compile time.
print(birthYear); // Outputs: 1990
}void main() {
const List<int> fibonacciNumbers = [0, 1, 1, 2, 3, 5, 8, 13];
print(fibonacciNumbers); // Outputs: [0, 1, 1, 2, 3, 5, 8, 13]
// fibonacciNumbers[0] = 1; // This would result in a compile-time error.
}class ImmutablePoint {
final int x;
final int y;
const ImmutablePoint(this.x, this.y);
}
void main() {
const ImmutablePoint origin = ImmutablePoint(0, 0);
print(origin.x); // Outputs: 0
// origin.x = 1; // This would result in a compile-time error.
}const int iterations = 1000; // A compile-time constant example.final int startTime = DateTime.now().millisecondsSinceEpoch; // A runtime example.class User {
final String userID;
User(this.userID);
}class Circle {
static const double pi = 3.14159; // A compile-time constant.
}