Jump to page: 1 2
Thread overview
const or immutable?
Sep 22, 2021
Ali Çehreli
Sep 22, 2021
Adam D Ruppe
Sep 22, 2021
Ali Çehreli
Sep 22, 2021
Timon Gehr
Sep 23, 2021
Ali Çehreli
Sep 22, 2021
Kagamin
Sep 23, 2021
Ali Çehreli
Sep 23, 2021
Mathias LANG
-preview=in (was: Re: const or immutable?)
Sep 23, 2021
Ali Çehreli
Sep 23, 2021
Temtaime
Re: -preview=in
Sep 24, 2021
Ali Çehreli
Sep 24, 2021
Ali Çehreli
Sep 23, 2021
Alexandru Ermicioi
Sep 24, 2021
H. S. Teoh
Sep 25, 2021
Ali Çehreli
Sep 28, 2021
Kagamin
September 22, 2021
Please excuse this thread here, which I know belongs more to the Learn forum but I am interested in the opinions of experts here who do not frequent that forum.

tl;dr Do you use 'const' or 'immutable' by-default for parameters and for local data?

Long version:

It was simpler in C++: We would make everything 'const' as much as possible. Unfortunately, that doesn't work in D for at least two reasons:

1) There is 'immutable' as well

2) There is no head-const (i.e. no 'const' pointer to mutable data; i.e. "turtles all the way down")

Further complications:

- Reference semantics versus copy semantics; by type (e.g. slices), by the 'ref' keyword, by a member of a struct that has reference semanticts (struct is by-copy; but a member may not be), etc.

- 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.

- Parameters versus local data.

I am seeking simple guidelines like C++'s "make everything const."

Let's start with what I like as descriptions about parameters:

1) 'const' parameter is "welcoming" because it can work with mutable, 'const', and 'immutable'. It (sometimes) means "I am not going to mutate your data."

2) 'immutable' parameter is "selective" because it can work only with 'immutable'. It means "I require immutable data."

But it's not that simple because a 'const' parameter may be a copy of the argument, in which case, it means "I will not mutate *my* data." This is actually weird because we are leaking an implementation detail here: Why would the caller care whether we mutate our paramener or not?

// Silly 'const':
void foo(const int i) {
  // ...
}

But of course it matters if the type has indirections (e.g. a member is a reference to some other data).

Aside: If 'const' is welcoming, why do we type 'string' for string parameters when we don't actually *require* immutable:

// Unenecassary 'immutable' but I do this everywhere.
void printFirstChar(string s) {
  write(s.front);
}

It should have better been const:

void printFirstChar(const char[] s) {
  write(s.front);
}

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:

  static assert(isInputRange!(typeof(s)));  // Fails. :(

So only the elements of the string should be 'const':

void printFirstChar(const(char)[] s) {
  write(s.front);
  static assert(isInputRange!(typeof(s)));  // Passes.
}

(Granted, why is it 'const' anyway? Shouldn't printFirstChar be a function template? Yes, it should.)

So, what are your guidelines here?

More important to me: How do you define your local data that should not be mutated?

  const     c = SomeStruct();
  immutable i = SomeStruct();

In this case, 'const' is not "welcoming" nor 'immutable' is "selective" because these are not parameters; so, the keywords have a different meaning here: With local data, they both mean "do not mutate". Is 'immutable' better here because we may pass that data to an immutable-requiring function? Perhaps we should learn from string and make it really 'immutable'? But it's not the same because here 'immutable' applies to the whole struct whereas 'string's immutability is only with its elements. There! Not simple! :)

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.

Thank you,
Ali
September 22, 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?

well i often ignore it but `const` (or often better yet, `inout` if there's any relation to a member of yours or if you return it) is better on function arguments, and `immutable` is better on internal members.

The exception is when you are storing something from someone else, where immutable params might be more important. But if you are looking and not touching, const or `in` is good.

> Aside: If 'const' is welcoming, why do we type 'string' for string parameters when we don't actually *require* immutable:

Unfortunate naming here... the convenient name encourages its use but it is indeed often suboptimal.

September 22, 2021
On 9/22/21 1:17 PM, Adam D Ruppe wrote:
> 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?
>
> well i often ignore it but `const` (or often better yet, `inout` if
> there's any relation to a member of yours or if you return it)

'inout' has been useful in cases like you mention. For example, a slice's elements should reflect the mutability of 'this'. I am mostly interested in revising a beginner-oriented chapter, where 'inout' doesn't exist yet.

> is better
> on function arguments, and `immutable` is better on internal members.
>
> The exception is when you are storing something from someone else, where
> immutable params might be more important.

I noticed that as well: For example, if a file name constructor parameter is string, I don't need to copy the file name to a member. (This actually appears later in the book where I realize constructor arguments might better be 'immutable'.)

> But if you are looking and not
> touching, const or `in` is good.

'in' has been another question: It appears a lot in my book but I almost never use it myself. Perhaps that's the answer? I would like that. However, the proposed change of meaning of 'in' is also worrying but I think it will strenghthen the case for it's use I guess.

>> Aside: If 'const' is welcoming, why do we type 'string' for string
>> parameters when we don't actually *require* immutable:
>
> Unfortunate naming here... the convenient name encourages its use but it
> is indeed often suboptimal.

I agree. However, ironically and worryingly :), it just works most of the time.

Ali


September 22, 2021
On 22.09.21 22:06, Ali Çehreli wrote:
> Please excuse this thread here, which I know belongs more to the Learn forum but I am interested in the opinions of experts here who do not frequent that forum.
> 
> tl;dr Do you use 'const' or 'immutable' by-default for parameters and for local data?
> 
> ...

No. I think `const` and especially `immutable` are great for plain old data types, especially data that has multiple references to it. For non-trivial user-defined data types, I think just using proper encapsulation is usually sufficient and more flexible.
September 22, 2021
My guideline is that most stuff is mutable, so there's little reason to try to make it const. For most simple types like scalars and arrays const is relatively cheap and useful, but its usefulness decreases with complexity of the type, the more complex is the type the more likely it's mutable. Strings often benefit from immutability.
September 23, 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.

>

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:

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.

September 22, 2021
On 9/22/21 6:29 PM, Mathias LANG wrote:

> alias cstring = const(char)[];

I've been thinking about that myself. Good to see it being used.

> - Enable `-preview=in` (with DMD >= v2.095) and pass parameters by `in`

2.094 perhaps?

  https://dlang.org/changelog/2.094.0.html#preview-in

Yeah, this meaning of 'in' makes a lot of sense to me. In fact, Adam mentioned 'in' as well. I can make it "standard" by recommending it in the book. >:)

Ali

P.S. I will respond to most other comments after digesting them a bit. :)
September 23, 2021
On Wednesday, 22 September 2021 at 20:06:59 UTC, Ali Çehreli wrote:
> ....

imho they can be interpreted like this for parameters:
const: I won't change it.
immutable: I won't change it, and expect no one else to change it too.

Regards,
Alexandru.
September 23, 2021
On Thursday, 23 September 2021 at 03:31:01 UTC, Ali Çehreli wrote:
> On 9/22/21 6:29 PM, Mathias LANG wrote:
>
>> alias cstring = const(char)[];
>
> I've been thinking about that myself. Good to see it being used.
>
>> - Enable `-preview=in` (with DMD >= v2.095) and pass parameters by `in`
>
> 2.094 perhaps?
>
>   https://dlang.org/changelog/2.094.0.html#preview-in
>
> Yeah, this meaning of 'in' makes a lot of sense to me. In fact, Adam mentioned 'in' as well. I can make it "standard" by recommending it in the book. >:)
>
> Ali
>
> P.S. I will respond to most other comments after digesting them a bit. :)

Surely, i'm using 'in' anywhere i need const. I'm avoiding using explicit const/immutable.
September 23, 2021
On 9/22/21 1:48 PM, Timon Gehr wrote:

> No. I think `const` and especially `immutable` are great for plain old
> data types, especially data that has multiple references to it. For
> non-trivial user-defined data types, I think just using proper
> encapsulation is usually sufficient and more flexible.

You mean, providing "const access" to an encapsulated entity through an accessor. Sure... Still, do you make 'const' local objects of such types or are they auto?

  const     a = MyStruct();
  immutable b = MyStruct();
  auto      c = MyStruct();  // This one?

Ali

« First   ‹ Prev
1 2