January 27, 2022
On 1/8/22 05:23, Imperatorn wrote:
> On Saturday, 8 January 2022 at 02:07:10 UTC, Ali Çehreli wrote:
>> 1) After about three years, I finally added copy constructors:
>>
>> http://ddili.org/ders/d.en/special_functions.html#ix_special_functions.copy%20constructor 


> Will the physical book also be updated?

Yes, now all copies are updated:

- Hardcopy by IngramSpark

- Paperback by IngramSpark and KDP (I checked on Amazon, the "Look Inside" feature shows the new content (at least the COpyright page))

- Ebooks on KDP, GumRoad, and Draft2Digital

There are three years' worth of corrections and edits as well. I've also removed the "First Edition" misnomer from the book; it is obviously continuosly updated.

An of course, everything is free here:

  http://ddili.org/ders/d.en

I have a minor update to that page as well, which should be live soon.

Aside: The D Language Foundation has an Amazon Associate link to the book:

  https://www.amazon.com/dp/1515074609?&tag=dlang-20

And many other ways to take donations:

  https://dlang.org/foundation/donate.html

Ali

January 27, 2022
On Thursday, 27 January 2022 at 15:30:02 UTC, Ali Çehreli wrote:
> On 1/8/22 05:23, Imperatorn wrote:
> > [...]
> wrote:
> >> [...]
> constructors:
> >> [...]
> http://ddili.org/ders/d.en/special_functions.html#ix_special_functions.copy%20constructor
>
> [...]

Wonderful, thanks for your highly valuable contributions
February 09, 2022
On Saturday, 8 January 2022 at 02:07:10 UTC, Ali Çehreli wrote:
> 2) The other noteworthy change in the book is my now-different stance on variables: Now I recommend 'const' over 'immutable' for variables.

I'm curious, could you elaborate a bit on this? I skimmed through the page on Immutability but I didn't find anything explaining it.
February 09, 2022
On 2/9/22 02:15, Anonymouse wrote:
> On Saturday, 8 January 2022 at 02:07:10 UTC, Ali Çehreli wrote:
>> 2) The other noteworthy change in the book is my now-different stance on variables: Now I recommend 'const' over 'immutable' for variables.
> 
> I'm curious, could you elaborate a bit on this? I skimmed through the page on Immutability but I didn't find anything explaining it.

To make sure we are looking at the same version, the new content has the following section:

<quote>
in parameters

As we will see in the next chapter, in implies const and is more useful with the ‑preview=in command line switch. For that reason, I recommend in parameters over const parameters.
</quote>

  http://ddili.org/ders/d.en/const_and_immutable.html

In short, in the past, under the influence of "stronger guarantee" clearly sounding better, I was recommending immutable for variables:

  immutable i = 42;

Realizing that I don't do that in my own code, now I recommend const:

  const i = 42;

Further, now that we have -preview=in (thanks to Mathias Lang), I recommend 'in' over const for parameters. (Actually, I had always recommended 'in' and did use it in some of the examples in the book (inconsistently) but I wasn't using it in my own code.)

But the whole thing is complicated. :) So, I asked for and received opinions on this thread:

  https://forum.dlang.org/thread/sig2d4$657$1@digitalmars.com

I don't find the common description helpful:  "immutable provides stronger guarantee." That's true and sounds better than mere "strong" but it does not help a programmer with deciding on which one to use. Rather, I like the following distinction:

- const is a promise

- immutable is a requirement

Now, that helps me decide which one to use when. Let's start with a function:

void foo(in A a, immutable B b) {
  // ...
}

There, foo "promises" to not mutate 'a' ('in' implies 'const') and "requires" that 'b' is not mutated by any other code.

With that understanding, it is silly to "require" that no other code mutates a local variable:

void x() {
  immutable b = B();  // Really? Who can mutate it?
  // ...
}

So, the following is logical (and shorter :) ):

void x() {
  const b = B();
  // ...
}

Now, if I need to pass it to a function that "requires" immutable, of course I will have to define it as immutable to satisfy that requirement:

void x() {
  immutable b = B();
  foo(A(), b);
  // ...
}

Aside: One of the confusions is that 'const' would work there as well *if* B did not have any indirection. But if it's defined as e.g.

struct B {
  int[] arr;
}

then, x() will not compile with 'const b'.

In summary:

Variables: 'const' by default, 'immutable' as needed (because somebody requires it)

Parameters: 'in' by default, 'immutable' if required

That's my view. I think there are others who disagree with parts of it and that's why this issue is surprisingly complicated.

Ali
February 09, 2022
On Wed, Feb 09, 2022 at 10:28:15AM -0800, Ali Çehreli via Digitalmars-d-announce wrote: [...]
> - const is a promise
> 
> - immutable is a requirement
[...]

Strictly speaking, that's not really an accurate description. :-P  A more accurate description would be:

- const: I cannot modify the data (but someone else might).

- immutable: I cannot modify the data, AND nobody else can either.

The best way I've found to understand the relationship between const, immutable, and mutable in D is the following "type inheritance" diagram (analogous to a class inheritance diagram):

	          const
	         /     \
	(mutable)       immutable

Const behaves like the "base class" (well, base type, sortof) that either mutable or immutable can implicitly convert to. Mutable and immutable, however, are mutually incompatible "derived classes" that will not implicitly convert to each other. (Of course, the reality is somewhat more complex than this, but this is a good, simple conceptual starting point to understanding D's type system.)

Const means whoever holds the reference to the data cannot modify it. So it's safe to hand them both mutable and immutable data.

Mutable means you are allowed to modify it, so obviously it's illegal to pass in const or immutable.  Passing mutable to const is OK because the recipient cannot modify it, even though the caller himself may (since he holds a mutable reference to it).

Immutable means NOBODY can modify it, not even the caller. I.e., *nobody* holds a mutable reference to the data. So you cannot pass mutable to immutable.  Obviously, it's safe to pass immutable to const (the callee cannot modify it anyway, so we're OK).  But you cannot pass const to immutable, because, as stated above, a const reference might be pointing to mutable data: even though the holder of the reference cannot himself modify it, it may have come from a mutable reference somewhere else. Allowing it would break the rule that immutable means *nobody* has a mutable reference to the data. So that's not allowed.

IOW, "downcasting" in the above "type hierarchy" is not allowed, in general.

However, there's a special case where mutable does implicitly convert to mutable: this is if the mutable reference is unique, meaning that it's the only reference that exists to that data. In such a case, it's OK to convert that mutable reference to an immutable one, provided the mutable reference immediately goes out of scope. After that point, nobody holds a mutable reference to it anymore, so it fits into the definition of immutable. This happens when a mutable reference is returned from a pure function:

	MyData createData() pure {
		MyData result; // N.B.: mutable
		return result;
		// mutable reference goes out of scope
	}

	// OK: function is pure and reference to data is unique
	immutable MyData data = createData();

The `pure` ensures that createData didn't cheat and store a mutable reference to the data in some global variable, so the reference to MyData that it returns is truly unique. So in this case we allow mutable to implicitly convert to immutable.

There's also another situation where immutable is allowed to implicitly convert to mutable: this is when the data is a by-value type containing no indirections. Essentially, we're making a copy of the immutable data, so it doesn't matter if we modify the copy, since we're not actually modifying the original data.

//

Now, w.r.t. the original question of when we should use const vs. immutable:

- For local variables, there's no practical difference between const and
  immutable, because by definition the current function holds the only
  reference to it, so there can't be any mutable reference to it. I
  would just use immutable in this case -- the compiler may be able to
  optimize the code better knowing that there can't be any mutable
  reference anywhere else (though in theory the compiler should have
  already figured this out, since it's a local variable).

- For function parameters, I would always use const over immutable,
  unless there was a reason I want to guarantee that nobody else holds a
  mutable reference to that argument (e.g., I'm storing the reference in
  a data structure that requires the data not to be mutated afterwards).
  Using const makes the function usable with both mutable and immutable
  arguments, which is more flexible when you don't need to guarantee
  that the data will never be changed by anybody.

  Some people may prefer `in` instead of const for function parameters:
  it's more self-documenting, and if you use -preview=in, it means
  `const scope`, which adds an additional check that you don't
  accidentally leak reference to parameters past the scope of the
  function.


T

-- 
Windows 95 was a joke, and Windows 98 was the punchline.
February 10, 2022
Why do we even bother with `in` when we can do:

alias In(T) = const scope T;

void test(In!int n) {
    pragma(msg, typeof(n));
}

?

onlineapp.d(3): Deprecation: storage class `scope` has no effect in type aliases
const(int)

...oh
February 09, 2022
On 2/9/22 18:11, Meta wrote:
> Why do we even bother with `in` when we can do:
>
> alias In(T) = const scope T;
>
> void test(In!int n) {
>      pragma(msg, typeof(n));
> }
>
> ?
>
> onlineapp.d(3): Deprecation: storage class `scope` has no effect in type
> aliases
> const(int)
>
> ...oh

I didn't know that but 'in' is underrated. There is heavy mental load on deciding parameter types:

// Silly const:
void foo(const(int));

// Too much information to the user (why
// do they need to know that I will mutate the parameter):
void foo(int);

// When I know that copying is expensive
// (which excludes rvalues; oops):
void foo(ref const(ExpensiveToCopy));

// When I know that the type is non-copyable,
// I have to use 'ref':
void foo(ref const(NonCopyable));

What if foo is a template? ref or const or by-value? Or inout? Always or sometimes?

Enough already! :)

All I want to say is "I want to use this parameter as input." I don't care if its rvalue or expensive to copy or impossible to copy. I will define it as 'ref' if that's what I want but I shouldn't be thinking about any of the above for function inputs.

I am happy to raise awareness of the new 'in':

  https://dlang.org/spec/function.html#in-params

'in' allows passing rvalues by ref! 'in' eliminates unwanted side-effects just because a function wants to use an object. 'in' passes non-copyable types by reference. Wow! That's engineering to my ears. :)

Having said that, there is one thing that bothers me with 'in' or 'const'. Let's assume I want to mutate a copy of the parameter:

void foo(in int i) {
  ++i;    // ERROR
}

So I must make a copy:

  auto j = i;
  ++j;    // ERROR

Because 'auto' is too safe and takes 'const'. One more try:

  int j = i;
  ++j;

Or perhaps in some generic code:

  import std.traits : Unqual;
  Unqual!(typeof(i)) j = i;
  ++j;

Ok, fine.

One more thing remains: Although 'i' may be the most logical name for the parameter, I cannot name 'j' as 'i' so I can mangle the parameter name just to prevent using 'i' in the function by accident:

void foo(in int i_);

That's not good because it changes what my callers see of my function.

I can use 'i_' in the body (instead of 'j') but then I am open to the same mistake of using 'i' instead of 'i_' in the body. (Obviously not when mutating but when actually using.)

Yeah, that issue bugs me a little.

Ali

February 10, 2022

On Thursday, 10 February 2022 at 02:39:11 UTC, Ali Çehreli wrote:

>

Yeah, that issue bugs me a little.

Ali

I think an immediate improvement we could make to ease people's life is to make auto peel the outermost qualifier level inside functions.

So that:

const int* ptr;
auto p2 = ptr;
static assert(is(typeof(p2) == const(int)*));

I really can't think of any downside to it, only upsides:

  • It is still predictable / consistent;
  • It might reduce the number of template instantiations in some cases;
  • It just flows more naturally... If you want full constness, there's still const;

Likewise, I've been tinkering with having const* p2 = ptr to allow p2 to be mutated. But it has far more implications and the benefits aren't as clear. For auto, I think the case is pretty straightforward.

Fun fact: C++ mangling actually does that.

extern(C++) void foo (const int a);
extern(C++) void bar (int a);

pragma(msg, foo.mangleof); // _Z3fooi not _Z3fooKi
pragma(msg, bar.mangleof); // _Z3bari

P.S: Thanks for spreading the love about -preview=in. I haven't moved forward with enabling it by default yet, because it just does exactly what we want it to do and has never bothered us, so we tend to forget it's not the default. I shall however start to move ahead now that the switch has been available for a few releases.

February 10, 2022
On 2/10/2022 12:06 AM, Mathias LANG wrote:
> I think an *immediate* improvement we could make to ease people's life is to make `auto` peel the outermost qualifier level inside functions.
> 
> So that:
> ```D
> const int* ptr;
> auto p2 = ptr;
> static assert(is(typeof(p2) == const(int)*));
> ```
> 
> I really can't think of any downside to it, only upsides:
> - It is still predictable / consistent;
> - It *might* reduce the number of template instantiations in some cases;
> - It just flows more naturally... If you want full constness, there's still `const`;

It sounds sensible to me.
February 10, 2022
On 2/10/2022 12:34 PM, Walter Bright wrote:
> On 2/10/2022 12:06 AM, Mathias LANG wrote:
>> I think an *immediate* improvement we could make to ease people's life is to make `auto` peel the outermost qualifier level inside functions.
>>
>> So that:
>> ```D
>> const int* ptr;
>> auto p2 = ptr;
>> static assert(is(typeof(p2) == const(int)*));
>> ```
>>
>> I really can't think of any downside to it, only upsides:
>> - It is still predictable / consistent;
>> - It *might* reduce the number of template instantiations in some cases;
>> - It just flows more naturally... If you want full constness, there's still `const`;
> 
> It sounds sensible to me.


By the way, `cast()ptr` will peal off the outermost const/immutable/etc from the type.