www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Best practices of using const

reply envoid <envoid cool-mail.com> writes:
In C++ we have const correctness in some way. A compiler can make 
optimizations whenever it doesn't find a const_cast and the 
mutable specifier marks members that aren't a part of the object 
state. Of course, it's not perfect but one can document their 
intentions and it's possible to use synchronization primitives 
without an issue. On the opposite side, D has a stricter 
(transitive) const and it's almost useless in many cases. Is 
there an article that explains best practices of using const in 
D? The statement "In C++ const isn't transitive so we fixed 
that." alone isn't convincing. The only way I see right now is 
omitting the const keyword completely which is ridiculous.
Feb 13
next sibling parent Kagamin <spam here.lot> writes:
D has immutable data, const allows to consume both mutable and 
immutable data.
Feb 13
prev sibling next sibling parent XavierAP <n3minis-git yahoo.es> writes:
On Wednesday, 13 February 2019 at 11:32:46 UTC, envoid wrote:
 Is there an article that explains best practices of using const 
 in D?
Chapter 8 of Andrei Alexandrescu's book The D Programming Language.
Feb 13
prev sibling next sibling parent Alex <sascha.orlov gmail.com> writes:
On Wednesday, 13 February 2019 at 11:32:46 UTC, envoid wrote:
 Is there an article that explains best practices of using const 
 in D?
http://jmdavisprog.com/articles/why-const-sucks.html
Feb 13
prev sibling next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Feb 13, 2019 at 11:32:46AM +0000, envoid via Digitalmars-d-learn wrote:
 In C++ we have const correctness in some way. A compiler can make
 optimizations whenever it doesn't find a const_cast and the mutable
 specifier marks members that aren't a part of the object state. Of
 course, it's not perfect but one can document their intentions and
 it's possible to use synchronization primitives without an issue. On
 the opposite side, D has a stricter (transitive) const and it's almost
 useless in many cases. Is there an article that explains best
 practices of using const in D? The statement "In C++ const isn't
 transitive so we fixed that." alone isn't convincing. The only way I
 see right now is omitting the const keyword completely which is
 ridiculous.
Const in D is very restrictive because it's supposed to provide real compiler guarantees, i.e., it's statically verifiable that the data cannot be changed. Unfortunately, that guarantee also excludes a lot of otherwise useful idioms, like objects that cache data -- a const object cannot cache data because that means it's being mutated, or lazily-initialized objects -- because once the ctor has run, the object can no longer be mutated. Most notably, D's powerful range idiom is pretty much unusable with const because iteration over a range requires mutating the range (though having const *elements* in a range is fine). This doesn't seem as bad at first glance, but it wreaks havoc on generic code, another thing that D is purportedly good at. It's very hard (and often impossible) to write generic code that works with both const and mutable objects. In practice, I've found that using const is really only sustainable at the lowest levels of code, to guarantee low-level non-mutability of PODs and other low-level objects. It's also useful for representing a reference to data that could be either mutable or immutable, in this "type inheritance" diagram that's very helpful for D learners to understand how D's const system works: const / \ mutable immutable I.e., mutable and immutable are implicitly convertible to const, but const is not implicitly convertible to either. Immutable in D is a hard guarantee that the data cannot ever be changed by anyone in any thread. Const means the holder of the const reference cannot mutate it, but a 3rd party could possibly hold a mutable reference to it and mutate it that way. So it's a somewhat weaker guarantee. But that's beside the point. The point is that when your code doesn't touch the data but you want to be able to pass both mutable and immutable arguments to it, const is the ticket. But given the restrictiveness of const, it's a rare occasion when you actually have to do this. The most notable exception being D strings, which are defined to be immutable(char)[], i.e., a (mutable) array of immutable chars (meaning the array itself can be changed, e.g., by slicing, changing length, etc., but the underlying char data is immutable). Some of my own projects use const(char)[] quite often, in order for the code to be able to accept both mutable char[] and string (i.e., immutable(char)[]). Outside of this, I only use const rarely, maybe in the occasional query method in a low-level type where I'm sure mutation will never be necessary. Even in such cases, I rarely use const, because it's infectious and a seemingly small change of adding const to a getter method sometimes percolates throughout the entire codebase and requires const correctness everywhere else, usually ending in a stalemate when it reaches something like a range that needs to be mutable and cannot be made const without onerous refactoring. It's *possible* in theory to make everything const-correct, but it's quite onerous and honestly only of limited benefit relative to the sheer amount of effort required to pull it off. So most of the time I just don't bother except in the lowest levels of code where the scope of const's infectiousness is limited. So ironically, the iron-clad semantics of D's const system turns out to be also its own downfall. T -- Obviously, some things aren't very obvious.
Feb 13
next sibling parent envoid <envoid cool-mail.com> writes:
Thank you for such a comprehensive answer.
Feb 14
prev sibling next sibling parent Marco de Wild <mdwild sogyo.nl> writes:
On Wednesday, 13 February 2019 at 16:40:18 UTC, H. S. Teoh wrote:
 On Wed, Feb 13, 2019 at 11:32:46AM +0000, envoid via 
 Digitalmars-d-learn wrote:
 Unfortunately, that guarantee also excludes a lot of otherwise 
 useful idioms, like objects that cache data -- a const object 
 cannot cache data because that means it's being mutated, or 
 lazily-initialized objects -- because once the ctor has run, 
 the object can no longer be mutated. Most notably, D's powerful 
 range idiom is pretty much unusable with const because 
 iteration over a range requires mutating the range (though 
 having const *elements* in a range is fine).  This doesn't seem 
 as bad at first glance, but it wreaks havoc on generic code, 
 another thing that D is purportedly good at. It's very hard 
 (and often impossible) to write generic code that works with 
 both const and mutable objects.

 So ironically, the iron-clad semantics of D's const system 
 turns out to be also its own downfall.


 T
I agree that const by nature unfortunately kills lazy initialization. However, I don't really understand why const is a problem with ranges. Const elements are not a problem. Iterating over a range consumes it (if I understand correctly). It does not make sense to be able to consume a const object, so from my point of view it's perfectly logical to disallow iterating const ranges. If I'm missing something, please correct me. I use const quite thoroughly in my project (a mahjong board game) and in fact I am writing a blog post explaining how it helped me understand what was happening in my code base. It enforces encapsulated mutations. In classic OOP languages, mutable objects propagate through the entire system, unless you actively create an immutable copy of it (which is a lot of work for little gain). If someone modifies your object on a place you don't expect (e.g. creating and persisting data when rendering a read-only view), it becomes hard to impossible to reason about the problem and debug it. Refactoring in const was a lot of work, but I think it made my code better in the end. I didn't run into any problems when using it, except when I tried to modify an object where I should not have (e.g. sorting a hand when rendering the view). I was able to untangle the spaghetti because the compiler poked me about it. As I didn't run into any problems and it helped clean up my code base, I would recommend trying it.
Feb 17
prev sibling parent reply Atila Neves <atila.neves gmail.com> writes:
On Wednesday, 13 February 2019 at 16:40:18 UTC, H. S. Teoh wrote:
 On Wed, Feb 13, 2019 at 11:32:46AM +0000, envoid via 
 Digitalmars-d-learn wrote:
 [...]
Const in D is very restrictive because it's supposed to provide real compiler guarantees, i.e., it's statically verifiable that the data cannot be changed. [...]
I keep hearing how const is nigh unusable in D, and except for ranges I litter my code with const everywhere, pretty much just as often as I used in C++. I normally only use `auto` for return types and input ranges, and nearly all of my function parameters are `in`. It's true that a lot of people don't use `const` because I keep finding and filing bugs in dub libraries as soon as I try using them, but other than that: const is fine.
Feb 19
parent reply Kagamin <spam here.lot> writes:
On Tuesday, 19 February 2019 at 15:30:22 UTC, Atila Neves wrote:
 I keep hearing how const is nigh unusable in D, and except for 
 ranges I litter my code with const everywhere, pretty much just 
 as often as I used in C++.
I once spent a good amount of effort to annotate my code with pure and inout only to find a compiler bug, then I realized that annotations aren't really needed, because the collection is inherently mutable anyway (appender).
Feb 19
parent reply drug <drug2004 bk.ru> writes:
On 19.02.2019 19:19, Kagamin wrote:
 On Tuesday, 19 February 2019 at 15:30:22 UTC, Atila Neves wrote:
 I keep hearing how const is nigh unusable in D, and except for ranges 
 I litter my code with const everywhere, pretty much just as often as I 
 used in C++.
I once spent a good amount of effort to annotate my code with pure and inout only to find a compiler bug, then I realized that annotations aren't really needed, because the collection is inherently mutable anyway (appender).
I use const all over the place too. And I made PR to other libraries to add const qualifier. Yes, it sometimes forces me to make a copy of data to mutate it - but I'm pretty sure this is the purpose of the qualifier. This helps me to catch/prevent bug. So I don't agree with people who do not use const at all. Definitely const qualifier in D is usable and is useful. The same I can say about properties - for example I use them in meta programming to detect what to serialize/process - I skip methods but serialize properties and for me this is a nice language feature.
Feb 19
parent reply Kagamin <spam here.lot> writes:
On Tuesday, 19 February 2019 at 16:38:17 UTC, drug wrote:
 The same I can say about properties - for example I use them in 
 meta programming to detect what to serialize/process - I skip 
 methods but serialize properties and for me this is a nice 
 language feature.
Serialization of arbitrary stuff is a bad practice anyway, it was the cause of vulnerabilities in serialization libraries. DTO is the way to go.
Feb 20
parent drug <drug2004 bk.ru> writes:
On 20.02.2019 11:05, Kagamin wrote:
 On Tuesday, 19 February 2019 at 16:38:17 UTC, drug wrote:
 The same I can say about properties - for example I use them in meta 
 programming to detect what to serialize/process - I skip methods but 
 serialize properties and for me this is a nice language feature.
Serialization of arbitrary stuff is a bad practice anyway, it was the cause of vulnerabilities in serialization libraries. DTO is the way to go.
serialization is just an example here. But using properties lets me to avoid using DTO except really complex cases and lets me decrease maintenance cost. In my case (I develop a prototype and very often change its data structures) they work really well.
Feb 20
prev sibling parent psycha0s <box mail.com> writes:
On Wednesday, 13 February 2019 at 11:32:46 UTC, envoid wrote:
 Is there an article that explains best practices of using const 
 in D?
You can find some information here: https://dlang.org/articles/const-faq.html
Feb 13