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