How does the const constructor actually work?

in This video, You will know why we need it. https://www.youtube.com/watch?v=B1fIqdqwWw8&t=558s From 09:18 to 16:09


In Documentation: https://dart.dev/guides/language/language-tour

constant constructors. To create a compile-time constant using a constant constructor, put the const keyword before the constructor name:

> var p = const ImmutablePoint(2, 2);

Constructing two identical compile-time constants results in a single, canonical instance:

 var a = const ImmutablePoint(1, 1);
 var b = const ImmutablePoint(1,1);
   
 assert(identical(a, b)); // They are the same instance!

Within a constant context, you can omit the const before a constructor or literal. For example, look at this code, which creates a const map:

 // Lots of const keywords here.
 const pointAndLine = const {  
   'point': const [const ImmutablePoint(0, 0)],
   'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
 };

You can omit all but the first use of the const keyword:

 // Only one const, which establishes the constant context.
 const pointAndLine = {   
    'point': [ImmutablePoint(0, 0)],
    'line':  [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)], };

If a constant constructor is outside of a constant context and is invoked without const, it creates a non-constant object:

> var a = const ImmutablePoint(1, 1); // Creates a constant var b =
> ImmutablePoint(1, 1); // Does NOT create a constant
> 
> assert(!identical(a, b));// NOT the same instance!

For more information you can check these 2 Answers below:

1- https://stackoverflow.com/a/21746692/14409491

2- https://stackoverflow.com/a/21745617/14409491


Const constructor creates a "canonicalized" instance.

That is, all constant expressions begin canonicalized, and later these "canonicalized" symbols are used to recognize equivalence of these constants.

Canonicalization:

A process for converting data that has more than one possible representation into a "standard" canonical representation. This can be done to compare different representations for equivalence, to count the number of distinct data structures, to improve the efficiency of various algorithms by eliminating repeated calculations, or to make it possible to impose a meaningful sorting order.


This means that const expressions like const Foo(1, 1) can represent any usable form that is useful for comparison in virtual machine.

The VM only needs to take into account the value type and arguments in the order in which they occur in this const expression. And, of course, they are reduced for optimization.

Constants with the same canonicalized values:

var foo1 = const Foo(1, 1); // #Foo#int#1#int#1
var foo2 = const Foo(1, 1); // #Foo#int#1#int#1

Constants with different canonicalized values (because signatures differ):

var foo3 = const Foo(1, 2); // $Foo$int$1$int$2
var foo4 = const Foo(1, 3); // $Foo$int$1$int$3

var baz1 = const Baz(const Foo(1, 1), "hello"); // $Baz$Foo$int$1$int$1$String$hello
var baz2 = const Baz(const Foo(1, 2), "hello"); // $Baz$Foo$int$1$int$2$String$hello

Constants are not recreated each time. They are canonicalized at compile time and stored in special lookup tables (where they are hashed by their canonical signatures) from which they are later reused.

P.S.

The form #Foo#int#1#int#1 used in these samples is only used for comparison purposes and it is not a real form of canonicalization (representation) in Dart VM;

But the real canonicalization form must be "standard" canonical representation.


I find Lasse's answer on Chris Storms blog a great explanation.

Dart Constant Constructors

I hope they don't mind that I copy the content.

This is a fine explanation of final fields, but it doesn't really explain const constructors. Nothing in these examples actually use that the constructors are const constructors. Any class can have final fields, const constructors or not.

A field in Dart is really an anonymous storage location combined with an automatically created getter and setter that reads and updates the storage, and it can also be initialized in a constructor's initializer list.

A final field is the same, just without the setter, so the only way to set its value is in the constructor initializer list, and there is no way to change the value after that - hence the "final".

The point of const constructors is not to initialize final fields, any generative constructor can do that. The point is to create compile-time constant values: Objects where the all field values are known already at compile time, without executing any statements.

That puts some restrictions on the class and constructor. A const constructor can't have a body (no statements executed!) and its class must not have any non-final fields (the value we "know" at compile time must not be able to change later). The initializer list must also only initialize fields to other compile-time constants, so the right-hand sides are limited to "compile-time constant expressions"[1]. And it must be prefixed with "const" - otherwise you just get a normal constructor that happens to satisfy those requirements. That is perfectly fine, it's just not a const constructor.

In order to use a const constructor to actually create a compile-time constant object, you then replace "new" with "const" in a "new"-expression. You can still use "new" with a const-constructor, and it will still create an object, but it will just be a normal new object, not a compile-time constant value. That is: A const constructor can also be used as a normal constructor to create objects at runtime, as well as creating compile-time constant objects at compilation time.

So, as an example:

class Point { 
  static final Point ORIGIN = const Point(0, 0); 
  final int x; 
  final int y; 
  const Point(this.x, this.y);
  Point.clone(Point other): x = other.x, y = other.y; //[2] 
}

main() { 
  // Assign compile-time constant to p0. 
  Point p0 = Point.ORIGIN; 
  // Create new point using const constructor. 
  Point p1 = new Point(0, 0); 
  // Create new point using non-const constructor.
  Point p2 = new Point.clone(p0); 
  // Assign (the same) compile-time constant to p3. 
  Point p3 = const Point(0, 0); 
  print(identical(p0, p1)); // false 
  print(identical(p0, p2)); // false 
  print(identical(p0, p3)); // true! 
}

Compile-time constants are canonicalized. That means the no matter how many times you write "const Point(0,0)", you only create one object. That may be useful - but not as much as it would seem, since you can just make a const variable to hold the value and use the variable instead.

So, what are compile-time constants good for anyway?

  • They are useful for enums.
  • You can use compile-time constant values in switch cases.
  • They are used as annotations.

Compile-time constants used to be more important before Dart switched to lazily initializing variables. Before that, you could only declare an initialized global variable like "var x = foo;" if "foo" was a compile-time constant. Without that requirement, most programs can be written without using any const objects

So, short summary: Const constructors are just for creating compile-time constant values.

/L

[1] Or really: "Potentially compile-time constant expressions" because it may also refer to the constructor parameters. [2] So yes, a class can have both const and non-const constructors at the same time.

This topic was also discussed in https://github.com/dart-lang/sdk/issues/36079 with some interesting comments.


Very well explained in detail but for the users who are actually looking for the usage of a const constructor

It is used to Increase Flutter Performance as it helps Flutter to rebuild only widgets that should be updated.Means while Using setState() in StateFulWidgets, only those components will be rebuild that are non const constructor

Can be explained with example->

    class _MyWidgetState extends State<MyWidget> {

  String title = "Title";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Column(
        children: <Widget>[
          const Text("Text 1"),
          const Padding(
            padding: const EdgeInsets.all(8.0),
            child: const Text("Another Text widget"),
          ),
          const Text("Text 3"),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          setState(() => title = 'New Title');
        },
      ),
    );
  }
}

As In this example, only Text title should get changed, so only this widget should get rebuild, so making all the others widgets as const constructor will help flutter to do the same for increasing performance.

Tags:

Dart