const or immutable?
Mathias LANG
geod24 at gmail.com
Thu Sep 23 01:29:14 UTC 2021
On Wednesday, 22 September 2021 at 20:06:59 UTC, Ali Çehreli
wrote:
>
> tl;dr Do you use 'const' or 'immutable' by-default for
> parameters and for local data?
Short answer: `immutable` is a special case that is way too
prominent in D. `const` is my go-to *when I need it*.
> - It is said that 'immutable' is a stronger type of const,
> which at first sounds great because if 'const' is good,
> 'immutable' should be even better, right? Unfortunately, we
> can't make everything 'immutable' because 'const' and
> 'immutable' have very different meanings at least in parameter
> lists.
I think we should not fall for the fallacy of "more attributes ==
good".
More attributes just means more restrictions. Why do we need more
restrictions ?
To make our life easier when writing, reading, or changing code.
The less a piece of code can do, the easier it is to understand.
I think that fallacy started with `@safe`: It is good to make
everything `@safe`, right ? Can't go wrong with memory safety.
And `pure` also, because not using global is good, right ? Except
it starts to be very impractical for `pure`, so we have hacks
like save-set-reset `errno` to accommodate for it, because we
*want* things to be `pure`, although they mutate global state.
And then enters `nothrow`, and `@nogc`, and those categorically
shouldn't be treated the same as `@safe`. Not everything needs to
be `nothrow` / `@nogc`. And not everything needs to be constified.
> I am seeking simple guidelines like C++'s "make everything
> const."
I don't think it's [that
simple](https://youtu.be/qx22oxlQmKc?t=923).
> Aside: If 'const' is welcoming, why do we type 'string' for
> string parameters when we don't actually *require* immutable:
This is IMO "D's greatest mistake". When Sociomantic did the D1
-> D2 transition, we came up with three aliases:
```D
alias mstring = char[];
alias cstring = const(char)[];
alias istring = immutable(char)[];
```
Our guidelines were simple:
- Template Parameter ? => `istring`;
- Mutable buffer => `mstring` (usually `ref` too as we used
`assumeSafeAppend` a lot);
- Otherwise, it's most likely `cstring`;
The few exceptions I can remember were some constructors /
setters for things that we knew would be constructed once per
application lifetime (e.g. command-line parser).
Obviously, `cstring` ended up being used in most places. It's now
one of the first alias I define whenever I start a project that
will deal with strings.
> But wait! It works above only because 'front' happened to work
> there. The problem is, 's' is not an input range; and that may
> matter elsewhere:
IMO that's another problem. Ranges generally don't work (well)
with `const` (or `immutable`) qualified elements, nor do they
work well with non-copyable data.
> Or, should 'immutable' be preferable here because it's
> implicitly shared, which may be useful in the future?
>
> Are there simple guidelines around this topic? Please? :)
>
> Personally, I generally ignore 'immutable' (except, implicitly
> in 'string') both for parameters and local data.
If we go back to those keywords definition:
- `const`: Cannot be modified through this instance;
- `immutable`: Cannot be modified through *any* instance;
Now there's a very annoying (not so) corner case with the second
definition: It binds the lifetime of the memory to the lifetime
of *any* instance. Because if the data is destroyed, it means an
instance can modify it, which contradicts `immutable`. Luckily, D
has a GC, meaning we get away pretty easily with this... Until we
want every part to function independently and then we don't.
This was an intractable problem before `scope` had any effect,
because you could stack-allocate an `immutable` array and they
slice it, and boom! Now the only problem is that we need to
combine immutable and smart pointers, so that we can properly
free the memory whenever the last reference goes out of scope
(remember AfixAllocator and Andrei's talk?).
------
But I am digressing from your original question. Want a simple
set of guidelines ?
Here's what we do:
- Enable `-preview=in` (with DMD >= v2.095) and pass parameters
by `in` for functions (even for types without indirections);
- Avoid `in` for parameters which are stored (setters, ctors...);
- Use `const` for parameter with indirections if possible;
- Use `immutable` if needed (e.g. need to forward to a function
that uses `immutable`);
- Use mutable parameters otherwise;
- For local data, don't make it `const` or `immutable` unless
needed: If a function is so long that it doesn't fit on one's
screen, break it down;
It's simple, it works, it looks good.
More information about the Digitalmars-d
mailing list