Skip to main content
A constant constructor in Dart is a constructor declared with the const keyword that enables the creation of immutable, compile-time constant objects. When invoked in a constant context, it yields a canonicalized instance, meaning multiple instantiations with identical arguments reference the exact same memory address.

Syntax and Declaration

To define a constant constructor, prefix the constructor declaration with const.
class Vector2 {
  final double x;
  final double y;

  // Constant constructor declaration
  const Vector2(this.x, this.y);
}

Structural Constraints

Dart enforces strict compile-time rules for classes utilizing constant constructors:
  1. Immutable State: All instance variables within the class must be declared as final. Furthermore, fields cannot be declared as late; even late final instance variables are strictly prohibited.
  2. No Execution Body: The constructor cannot possess a block body ({}). State initialization must occur exclusively through formal parameters (this.field) or an initializer list.
  3. Potentially Constant Initializers: Expressions within the initializer list must be potentially constant expressions. Because constant constructors can be invoked at runtime, these expressions only evaluate as compile-time constants when the constructor itself is invoked in a constant context. Otherwise, they evaluate at runtime using the provided non-constant arguments.
  4. Inheritance Chain: If the class extends a superclass, the superclass must also expose a constant constructor. The subclass must invoke it, either explicitly via a super call in the initializer list, or implicitly if the superclass provides an unnamed, no-argument constant constructor.

Instantiation and Canonicalization

A constant constructor can be invoked in two ways, fundamentally altering its memory allocation behavior: 1. Compile-Time Constant (Canonicalized) When invoked with the const keyword (or inferred within a constant context), Dart evaluates the object at compile time. Identical arguments yield the exact same instance. All arguments passed to the constructor must also be compile-time constants.
const Vector2 v1 = Vector2(1.0, 2.0);
const Vector2 v2 = Vector2(1.0, 2.0);

print(identical(v1, v2)); // true: Both reference the same canonical instance
2. Runtime Instance (Non-Canonicalized) A constant constructor can still be invoked without the const keyword. This bypasses compile-time evaluation and canonicalization, allocating a new, distinct object in memory at runtime, though its fields remain immutable.
Vector2 v3 = Vector2(1.0, 2.0);
Vector2 v4 = Vector2(1.0, 2.0);

print(identical(v3, v4)); // false: Distinct instances allocated at runtime

Initializer List Mechanics

When using an initializer list with a constant constructor, operations are restricted to potentially constant expressions. You cannot call instance methods, invoke non-constant functions, or access non-constant values during initialization.
class Circle {
  final double radius;
  final double diameter;

  // Valid: 'radius * 2' is a potentially constant expression.
  // It evaluates at compile-time if invoked with 'const', 
  // or at runtime if invoked without 'const'.
  const Circle(this.radius) : diameter = radius * 2;
}

Factory Constant Constructors

A factory constructor can also be declared as const, provided it redirects to another constant constructor. It cannot execute arbitrary logic or return a cached instance manually, as the canonicalization is handled entirely by the Dart compiler.
class Logger {
  final String name;

  const Logger._internal(this.name);

  // Redirecting factory constant constructor
  const factory Logger(String name) = Logger._internal;
}
Tired of Poor Dart Skills? Fix That With Deep Grasping!Learn More