Why can't non-nullable fields be initialized in a constructor body in Dart?

491

Dart executes constructor bodies inside-out, from base class to derived class. This allows virtual dispatch to occur in the constructor body. The fact that virtual dispatch can occur in the constructor body means that the compiler cannot statically determine what code will be executed by the constructor body, and therefore it cannot deduce what the constructor body might ultimately initialize.

That the constructor body can execute arbitrary code (including callbacks) that might initialize members or that might depend on initialized members makes it even more complicated.

Furthermore, allowing members to be not initialized when the constructor body runs would be error-prone and a source for confusion. For example, with:

class SomeClass {
  int member;

  SomeClass() {
    updateMember(0);
  }

  void updateMember(int value) {
    print(value); // Oops.
    member = value;
  }
}

With Dart's current design, all instance methods (and their overrides) can be guaranteed that members are initialized when the method is called. If members were allowed to be uninitialized when the constructor body is executed, that would not longer be true, and all instance methods then would need to consider if they might be invoked from the constructor (or from a base class constructor), possibly indirectly from other method calls, and whether accessed members might not be initialized yet.

(I'll grant that that previous point isn't terribly strong since it currently can still happen that a member is initialized to an object that the constructor body must mutate, but typically instance methods receiving an empty List, Map, etc. is less of a problem than receiving uninitialized members. The above situation also could happen with late members, but that's the baggage that comes with choosing to use late.)

Null-safety disallows the possibility of accessing uninitialized non-late variables, but your proposal would make that possible.

Also, because there is a distinction between initializing members via an initializer list and via a constructor body, people are encouraged to use initializer lists when possible.

Share:
491
Titan
Author by

Titan

Updated on December 31, 2022

Comments

  • Titan
    Titan over 1 year

    Often times I have an instance field that needs to be initialized in a constructor. For example, it might be needed to be calculated based on other instance fields, hence I can't initialize it inline (where declared) or with a constructor initializer list.

    But it either has to be nullable, or declared late, if I need to initialize it in constructor. The rationale behind late keyword is that programmer states "I'll initialize this before using, trust me", when it can not be determined by the compiler that initialization will take place before first usage. BUT: this "programmer guarantee" seems A) terrible and B) unnecessary in case of constructors, because it can be determined by compiler whether the field was initialized in a constructor (and constructor itself is obviously guaranteed to execute before any other instance methods).

    Obvious downside to using late fields in such scenarios is that nothing enforces them compile-time to be actually initialized during construction (or anywhere, for that matter). Plus, every time the late field is read, a runtime check is inserted to make sure it has been assigned a value - I don't need that when I initialize in constructors.

    Therefore, it seems that, technically it should be possible to have non-nullable non-late fields that are initialized within a constructor body (and if they are not - compiler can throw an error).

    So what is the rationale of requiring constructor-initialized fields to be either nullable, or declared as late? Is there a technical reason why this limitation is imposed, or is it just a design oversight by the Dart team?

    • croxx5f
      croxx5f over 2 years
      Why cant you initialize it in an initializer list? Can you provide some simple example?
    • jamesdlin
      jamesdlin over 2 years
      The constructor body can execute arbitrary code (including instance methods) in any order. Therefore the object must be guaranteed to be initialized. See stackoverflow.com/q/63313116.
    • Titan
      Titan over 2 years
      @jamesdlin I don't see how this question is a duplicate of a linked one, or how it (completely) answers my question. Could you please elaborate (in a dedicated answer) why exactly is it impossible to deterministically initialize fields within a constructor body. And/or re-open my question.
    • venir
      venir over 2 years
      I know this doesn't answer your question, but consider using a factory constructor method if you really really need to implement a constructor body.