What is the difference between constructor and initializer list in Dart?

Viewed 13397

Problem

The output of the following two codes are the same, but what is the essential difference?

A Tour of the Dart Language - Initializer list

import 'dart:math';

class Point {
  final num x;
  final num y;
  final num distanceFromOrigin;

  Point(x, y)
      : x = x,
        y = y,
        distanceFromOrigin = sqrt(x * x + y * y);
}

main() {
  var p = Point(3, 4);
  print(p.distanceFromOrigin);
}

My code

  Point(this.x, this.y)
      : distanceFromOrigin = sqrt(x * x + y * y);

Both Outputs are the same 5.

Question

  • How should I use constructor and initializer list properly?

Development Environment

Best regards,

2 Answers

There is no difference, the result will be the same except that you can take advantage of different type of constructors.

In the case you don't want to expose your variables defined in Point and your mark those as private, the initializer would be a good option.

    class Point {
      final num _x;
      final num _y;
      final num _distanceFromOrigin;

      Point(x, y)
          : _x = x,
            _y = y,
            _distanceFromOrigin = sqrt(x * x + y * y);
    }

Also take a look to the constructor with optional parameters or factory constructors.

Whether by train or pram, once you reach the station, it makes no "essential" difference if only arriving at the destination matters.

Using "constructor" and "initializer list", you did get the same output, but that example is not the only use case supported by these features in Dart. Moreover, the shorter form (using "constructor" as an alternative to "initializer list") is just a frequent manifestation where Dart provided it as "syntactic sugar":

The pattern of assigning a constructor argument to an instance variable is so common, Dart has syntactic sugar to make it easy:

class Point {
  double x = 0;
  double y = 0;

  // Syntactic sugar for setting x and y
  // before the constructor body runs.
  Point(this.x, this.y);
}


I have used the terms "constructor" and "initializer list" apropos of your question/example, but to clearly mention the differences, I shall be using the following terminology for a broader perspective:

ClassName[.<constructor-name>](<constructor-args>) 
    : <initializer-list> {
  <constructor-body>
}

Until this part of the answer, the focus on "constructor" was largely disregarding the constructor's body, which happened to be the case since that example was probably meant to introduce initializer lists, as evident from the involvement of final members.


Final or Non-nullable Members

Constructor's body cannot be used to initialize non-nullable and/or final members. It could be done

  • at the member declaration site, or
  • directly through the constructor's arguments (using the this. syntactic sugar), or
  • necessarily the initializer list if some calculation is also involved (or using a factory constructor when the initialization logic cannot be handled in the initializer list)
class Demo {
  int x; // non-nullable member
  final int? y; // final member

  // ERROR (observe <constructor-body>'s limitation)
  Demo.constructor1(int x, int y) {
    this.x = x; 
    this.y = y;
  }

  // Allowed
  Demo.constructor2(int x, int y)
      : x = x,
        y = y;

  // Better (observe <constructor-args>'s syntactic sugar)
  Demo.constructor3(this.x, this.y);

  // ERROR
  Demo.constructor4(this.x, this.y = this.x + 1);

  // Allowed (observe <initializer-list>'s rescue)
  Demo.constructor5(this.x) : y = x + 1;
}

Aside, this does not make initializer list a replacement for constructor body since initializer lists are very limited in functionality.


Order of Execution

Another important difference is their order of execution, which could be significant from a design perspective, especially involving inheritance. It is fittingly documented in the language tour:

[...] the order of execution is as follows:

  1. initializer list
  2. superclass’s no-arg constructor
  3. main class’s no-arg constructor

If the superclass doesn’t have an unnamed, no-argument constructor, then you must manually call one of the constructors in the superclass. Specify the superclass constructor after a colon (:), just before the constructor body (if any).

Jamesdlin's comment contains a good summary.



How should I use constructor and initializer list properly?

Keeping the aforementioned differences in mind, "proper" usage objectively abides by such language constraints, but in cases where both the usage is appropriate, I reckon you are correct in utilizing the syntactic sugar for writing idiomatic Dart code unless required otherwise by something akin to an enforced style guide.

Related