Skip to main content
A constant constructor is a constructor declared with the const keyword that enables the creation of canonicalized instances. When invoked in a constant context, the constructor produces an instance known at compile time, allowing the Dart compiler to deduplicate identical instances into a single memory address.

Syntax and Declaration Requirements

To declare a constant constructor, the class and the constructor must adhere to specific immutability constraints:
  1. const Keyword: The constructor declaration must be prefixed with const.
  2. Immutable State: All instance variables (fields) within the class must be declared as final.
  3. No late Fields: Fields cannot be declared with the late modifier, as late implies runtime initialization.
  4. No Block Body: The constructor cannot have a body (curly braces {}). It must terminate with a semicolon, potentially following an initializer list.
  5. Superclass Constraints: If the class inherits from a superclass, the superclass constructor being invoked must also be a constant constructor.
class ImmutablePoint {
  // Requirement: All fields must be final and not late
  final double x;
  final double y;

  // Requirement: const keyword, no body, ends with semicolon
  const ImmutablePoint(this.x, this.y);
}

Initialization Lists and Potentially Constant Expressions

Constant constructors may use initializer lists to assign values to fields or call super constructors. However, expressions within the initializer list are restricted to potentially constant expressions. A potentially constant expression is an expression that can be evaluated at compile time if the constructor itself is invoked in a constant context. This allows the use of:
  • Literals (numbers, strings).
  • Constructor parameters.
  • Arithmetic operations involving other potentially constant expressions.
  • Invocations of other constant constructors.
It strictly prohibits executing arbitrary logic, non-constant function calls, or side effects.
class Circle {
  final double radius;
  final double area;

  // 'radius' is a parameter (potentially constant), so it is valid 
  // for use in the calculation of 'area' within the initializer list.
  const Circle(double radius) 
      : radius = radius,
        area = 3.14159 * radius * radius; 
}

Invocation and Instantiation Contexts

A constant constructor behaves differently depending on the context in which it is invoked. There are three scenarios:
  1. Explicit Constant Context: The constructor is explicitly invoked with the const keyword. This forces compile-time creation and canonicalization.
  2. Implicit Constant Context: The constructor is invoked without the const keyword, but resides within a context that requires a constant (such as a variable declared const or inside a const collection literal). Dart infers the const keyword, resulting in a compile-time canonicalized instance.
  3. Non-Constant Context: The constructor is invoked without const in a context where a constant is not required (e.g., assigned to a final or var variable). A new object is allocated at runtime.
// 1. Explicit Constant
// The 'const' keyword is applied directly to the constructor invocation.
var point1 = const ImmutablePoint(1, 2);

// 2. Implicit Constant 
// Dart infers 'const' for ImmutablePoint because the variable is 'const'.
const point2 = ImmutablePoint(1, 2);

// 3. Non-Constant (Runtime allocation)
// No 'const' keyword is present on the variable or the constructor.
final point3 = ImmutablePoint(1, 2);

Canonicalization

The primary mechanism of a constant constructor is canonicalization. The compiler ensures that for any given constant constructor invoked with identical arguments in a constant context, only one instance is created in memory. Variables holding constant instances with the same state refer to the exact same memory address.
void main() {
  // Canonicalized: Same arguments, same memory address
  var a = const ImmutablePoint(5, 10);
  var b = const ImmutablePoint(5, 10);
  
  // Runtime allocation: New memory address
  final c = ImmutablePoint(5, 10);

  // Identity check
  print(identical(a, b)); // true
  print(identical(a, c)); // false
}
Master Dart with Deep Grasping Methodology!Learn More