0

Looking at the Dart official documentation for the keywords final and const I find that:

"A final variable can be set only once; a const variable is a compile-time constant."

This is fine, but later, always the official documentation it says:

If your class produces objects that never change, you can make these objects compile-time constants. To do this, define a const constructor and make sure that all instance variables are final.

Which make sense, but then it drops a bomb:

Constant constructors don’t always create constants. For details, see the section on using constructors.

My question is, why we use the keyword const even if it might be executed not at compile-time? Wouldn't the keyword final be more appropriate for defining something that doesn't guarantee to run at compile-time and just promise to instantiate final variables? I feel the usage of const keyword there is wrong and misleading, even because in order to create a real constant we need to put const in front of the constructor's call or declare the variable we assign the value to as constant (this time rightly using const keyword).

zambotn
  • 735
  • 1
  • 7
  • 20
  • what !!!!!!!!!!! – OMi Shah May 09 '23 at 16:04
  • Sorry I placed the quotings wrongly – zambotn May 09 '23 at 16:36
  • What other keyword would you use to signal that a constructor *can* be used in constant expressions? – lrn May 09 '23 at 19:58
  • @Irn I don't know, at first I thought final, invariant, possiblyConst... but after jamesdlin [answer](https://stackoverflow.com/a/76211961/1089668) I think final is less suitable than the others... also, removing all keyword in the declaration of the constructor and just treat all constructors that matches the criteria as const would be even better. Afterall we can even see at compile time all the methods used in a const scope and verify if the const matches the requirements or not. – zambotn May 10 '23 at 06:16
  • @zambotn You can't just automatically treat all constructors as `const` if possible. That would lead to even worse API breakage: suppose you have a class that today has all `final` members but tomorrow adds a non-`final` member. Callers that incorrectly presumed it could be constructed in `const` contexts then would be broken. `const` declares a contract from the class author and signals intent. – jamesdlin May 10 '23 at 06:59
  • And perhaps you then might suggest that the `const` keyword should be removed entirely so that there are no explicit `const` contexts and that all objects should be constructed as compile-time constants whenever possible. However, [doing that could lead to other problems](https://stackoverflow.com/q/57607745/). – jamesdlin May 10 '23 at 07:03
  • One other thing: while I agree that `const` when used on a constructor is slightly misleading, in practice it's not much of a problem. As I mentioned, requiring that it be used *only* in `const` contexts would be far too restrictive, so most people don't usually expect that. Additionally, C++'s `constexpr` is fairly similar and it also specifies that something *can* be used in a constant expression, not that it *must* be. – jamesdlin May 10 '23 at 07:29
  • @jamesdlin yes, what i meant was what you describe in the later comment. The other problems you referenced are not big problems to me, just behaviours to be described in the documentation, i think i could live with them. Regarding the `constexpr` even if it is very similar: is more [reasonably explained](https://learn.microsoft.com/en-us/cpp/cpp/constexpr-cpp?view=msvc-170) and the keyword in this case stands for "constant _expression_", which semantically makes more sense to me, hinting that the values might not be constant, just the expression is. – zambotn May 10 '23 at 08:34
  • also i am agreeing that once developers learns _how it works_, even if the behavior is different from expected, they do not make many mistakes (aka people are not dumb): like in javascript even if `null == 0` is _false_ and `null > 0` is still _false_, `null >= 0` is evaluated as _true_. This doesn't make it the expected behaviour, tho. – zambotn May 10 '23 at 09:53

2 Answers2

1

A const constructor means that a constructor can be used to create a compile-time const object, not that it must. Being a const object is a property of the object (it cannot be mutated). Providing a const constructor is an important API decision that shouldn't be made lightly: changing a const constructor to a non-const one would be a breaking API change, so there is an expectation that mutators will never be added to the class.

Arguably const is a misleading keyword for a constructor since the same constructor can be used to create non-const objects. Alternatives would be to:

  • Use a different but more explicit keyword (e.g. constIfPossible). This seems undesirable because it's more verbose and because it requires adding a new keyword instead of re-using an existing one.

  • Require that classes provide separate constructors to create const and non-const objects. This either would lead to a lot of duplicated code or to classes that neglect (intentionally or not) to provide a const constructor without a non-const one, which usually would be unnecessarily limiting.

Neither option seems better.

It might be nice if there were an annotation that the linter recognized to explicitly disallow creating non-const instances, though.

Wouldn't the keyword final be more appropriate for defining something that doesn't guarantee to run at compile-time and just promise to instantiate final variables? I

A final constructor does not make much sense. Whether a caller's reference to the constructed object can be reassigned is a property of the caller's reference, not an intrinsic property of the object itself. Therefore a constructor should not dictate final-ness, and whether callers' references to the object are final or not makes no difference to the object or to its provided API.

jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • Yes, this make sense, but after all constructor doesn't dictate the const-ness of the reference of an instance either. What final would tell you is about the inner state of the instances generated by the constructor, not about its reference. Reference should rightly be treated with keywords at their initialization (either var, final or const). – zambotn May 10 '23 at 05:57
  • You rightly mentioned that removing const from a constructor in an api would break lot of things... but another alternative could be to treat as const all constructors that match the requirements to be potentially tagged so.. in this way we could remove the confusing keyword and just use it in the declaration (where is clear), if we need to make constant objects. This seems clearer and still allows the compiler to verify the const-ness at compile time. – zambotn May 10 '23 at 06:04
  • "What final would tell you is about the inner state of the instances generated by the constructor" This makes no sense. Whether a class declares its members as `final` or not is not conditional. If a class has `final` members, it doesn't need any special constructor or any special keyword. – jamesdlin May 10 '23 at 06:08
  • that's the point, so if the class has just initialised final members you are sure it cannot change so it _can_ be called in a `const` context. As I said in the other answer I am not in favour of `final` for any reasons, i didn't think deeply at it, just stating something as `const` and having it used on something that _can_ also not be initialised at compile-time feels so wrong to me and everything else feels better honestly. Then I agree that since even VIM has autocompletion and snippets, writing `constIfPossible` or `possiblyConst` wouldn't add so many keystrokes. – zambotn May 10 '23 at 07:24
  • A class having `final` members does not necessarily mean that it can be called in a `const` context. For example, the class's constructor might require an argument that is not `const`-constructible. – jamesdlin May 10 '23 at 07:25
0

The const constructor must be used in a const context and have const parameters for it to be a const. Used any other way, and it's just a non-const ordinary instance. Luckily, this will all be known at compile time, while you are editing the code, thanks to the analyzer constantly (heh) watching for the proper circumstances.

Randal Schwartz
  • 39,428
  • 4
  • 43
  • 70
  • I know that, but if it's the `const` context to make it constant (since it can also be used with non-constant parameters), why do we call it a constant constructor in the first place? The only thing the language enforces is having just final variables initialized at creation-time: this makes it final, not constant... – zambotn May 09 '23 at 16:41
  • Please read what I said carefully. It's not just having finals. It's having const values given to it, and being marked as const (directly or indirectly) in the invocation. – Randal Schwartz May 09 '23 at 19:52
  • But is not what the official documentation says: I literally cited all they said about it in the question. In particular, reading last sentence it's clear that to call it constant constructor it is not required to return constant objects, it can well be initialized and used with dynamic values, and still be called constant constructor – zambotn May 10 '23 at 05:35
  • Right. I don't know what you're disagreeing with now. :) It's called a constant constructor because the word const is in front of it, and it can make a constant under the right circumstances, but sometimes it's used to make a non-constant instance. – Randal Schwartz May 10 '23 at 06:01
  • Is clear how it works, my question was more on why they used the `const` keyword that previously was introduced for compile-time only... I was hoping to have a different keyword (in my question i proposed `final` without thinking too deeply but now i believe the other choices, like `invariant`, would have been a better) – zambotn May 10 '23 at 11:55