How often I should be using const? Is it useless/overrated?

H. S. Teoh hsteoh at qfbox.info
Fri Nov 18 17:57:25 UTC 2022


On Fri, Nov 18, 2022 at 11:51:42AM +0000, thebluepandabear via 
Digitalmars-d-learn wrote:
> A question I have been thinking about whilst using D is how 
> often I
> should be using const.

You should use it as often as you need to use it, and no more.  
If you
don't need to use it, don't use it.


> Many people claim that all variables should be const by 
> default, but
> whether or not it is really needed is debatable and oftentimes 
> making
> everything const could cause readability issues and make the 
> code more
> complex.

You're looking at it the wrong way.  The kind of issues having 
const
would solve is like when your function takes parameters x, y, z, 
and
somewhere deep in the function you see the expression `x + y*z`. 
If x,
y, and z are const, then you immediately know what the value of 
this
expression is.  However, if they were not, then you'd have to 
trace
through all of the preceding code to figure out whether their 
values
have changed, and how they have changed.  The former makes the 
code
easier to understand, the latter adds complexity to understanding 
the
code.

Now if you have a variable that's expected to change a lot, like 
an
accumulator of some complex calculation, then it makes no sense 
to make
it const.  Just modify it; you already have to understand most of 
the
code to figure out how the value is derived anyway, so having 
fewer
variables would actually make it easier to understand.  In this 
case,
using const is just counterproductive.

IOW, if const makes your code easier to understand and maintain, 
then go
for it.  If you're finding that you have to jump through hoops in 
order
to make your variables const, then probably it's *not* a suitable 
usage
in that case.


> I also wonder how important const really is, like 99% of the 
> time you
> will know when your variable will be changed, and even when a 
> variable
> changes it's not like it is the end of the world.

It totally *can* be the end of the world, if your variable is
controlling the launch of nuclear missiles, for example. :-P  
Having a
variable change when you don't expect it to -- that's a frequent 
source
of bugs and hard-to-understand code.  Again I say, it depends on 
what
it's being used for.  If it's used to hold a logically fixed value
(e.g., function argument passed from the caller that logically 
doesn't
change) then you want to use const.  But if you're using it to 
hold a
temporary value that's expected to change, then what's the point 
of
jumping through hoops just so you can brag "look, ma! my 
variables are
all const!"?


> Also, there is no real end to how much variables can be const.
> Oftentimes -- interestingly -- in popular repos I don't even 
> see const
> being used, this is what confuses me.
[...]

Because, honestly, const in D is a pain.  The reason is that D's 
const,
unlike const in C or C++, is transitive, meaning that if some 
object X
is const, then everything X refers to is also automatically 
const.  OT1H
this is wonderful: this means the compiler can actually make 
guarantees
about const and enforce it. You cannot just cast it away 
willy-nilly
without invoking UB, so the compiler can make optimizations based 
on the
assumption that some value never changes.  OTOH the fact that it's
transitive means that even if you have one tiny bit of data 10 
hops down
the line that you want to modify, you can't do it.  This greatly 
narrows
the applicability of const in D.  It's physical const, and cannot 
be
made logical const.

IME, const in D is mostly applicable to what I call "leaf node" 
data
structures: low-level structures where you're mostly dealing 
directly
with the underlying data or maybe 1-2 levels of abstraction away 
from
the physical data.  Once you get past that to higher level code, 
const
quickly becomes a giant pain to work with.  You cannot, for 
example,
lazy-initialize const data -- because that means you have to 
modify it
after the reference is already initialized, which is UB.  You 
cannot
cache data -- because you need to initialize the data when it's
referenced for the first time, and you need to set a flag to 
indicate
that the data is cached so that you don't have to load it again. 
There
are ways of working around this, but they are dirty and annoying 
to use.

In my own code, I rarely bother with const, except for the lowest 
levels
of code: modules that don't depend on anything else, so the scope 
is
easier to control and the number of situations to support is 
limited
enough that workarounds don't generate exponential amounts of
boilerplate.  In higher-level code I rarely bother with const. It 
often
excludes things you often do with complex code -- like caching, 
lazy
initialization, etc..  And errors with const in high-level code 
can
sometimes be a pain to track down -- you declare something const 
and
some leaf-node module 5 levels of abstraction down fails to 
compile.
Why?  You have to chase the code down 5 levels of abstraction to 
find
out.  It's just not worth the trouble.


Having said all that, though, one place where const/immutable 
really
shines in D is strings: because strings are defined to be
immutable(char)[], that means taking substrings never has to 
copy. This
saves a LOT of busywork copying bits of strings all over the 
place --
which you have to do in C/C++ because there char arrays are 
mutable, so
to make sure your substring doesn't unexpectedly mutate from 
under you,
you make a copy of it.  This leads to far more allocations than 
are
strictly necessary to perform some operation.  In D, you can 
freely
slice the strings as much as you want, and you're guaranteed that 
none
of them will unexpectedly mutate from under you.  None of them 
require
an allocation, and so are more efficient in string-processing 
code.


T

--
Маленькие детки - маленькие бедки.



More information about the Digitalmars-d-learn mailing list