On Mon, Aug 3, 2020 at 6:55 AM Andrei Alexandrescu via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
(Background: qualifiers were introduced following my horror when I
started writing D1 code and saw that strings are represented as char[].
So structs with string members would look like:

struct Widget
{
     int x, y;
     char[] name, parentName;
     ...
}

I found it just shocking that following a Widget's construction whoever
aliased the same strings the outside could change members of the Widget
without Widget knowing. Of course, other D1 coders disliked that as
well, so they'd defensively duplicate in the constructor:

struct Widget
{
     int x, y;
     char[] name, parentName;
     this(char[] n, char[] p)
     {
         name = n.dup;
         parentName = p.dup;
     }
     ...
}

thus ensuring proliferation of the garbage whether duplication was
needed or not.

I found this absolutely maddening, to the extent I didn't think D could
be ever used at any considerable scale while dragging this anchor behind it.

The second problem was Walter was adamant about using arrays of
characters at strings. He found the notion of a library-defined string
type (a la C++) an absolute abomination. Stubborn about it like I've
never seen him before or after. So my unstoppable requests for a string
type were met with the proverbial immovable refusal. Ironically, much of
his argument came from an efficiency angle, yet the unnecessary
duplication was way less efficient than some reference counting/small
string optimization/etc scheme that a dedicated string type would use.

Then we figured things would work out if we arranged things such that
people could NOT change individual characters of a string. That would
allow sharing without the danger of long-distance influence. After many
discussions with Walter, Bartosz, Eric, Brad, and myself, immutable and
const were born.

Then followed the other qualifiers, in order: shared and inout.)

* * *

The result is... there: https://dlang.org/spec/const3.html. It has the
images https://dlang.org/images/qualifier-combinations.svg and
https://dlang.org/images/qualifier-conversions.svg and a large table and
a lot of rules. Whenever I code anything generic, I find myself going
back to those damn images and tables way more than anyone ought to. (And
it's ironic... I made those. Woe to the relative newcomer.)

It's all too complicated, making generic D programming into 3D chess
instead of the difficult endeavor it already is. And what does it buy
us? Well, we don't need to define a string library type. Yowzers.
(Actually we should if we want to get rid of the GC. But then Walter
would oppose that. So - stalemate once again.)

Far as I can tell, the power/weight ratio of qualifier is very poor. I
wish a DIP would revisit qualifiers with a stated intent to simplify
them as much as possible. Whenever I code generically I invariably run
into these issues:

* Whatever I do, however I twitch, immutable finds the opportunity to
lodge itself in a soft part of my body and cause constant pain. This
doesn't work, that doesn't work. No solution for "tail immutable" -
mutable references to immutable class instances can't be done without
contortions. Can't assign to out immutable class references, though
there's no reason for that (see Adam's recent post).

* No matter how I dice any significant piece of code, there will be five
casts from immutable and/or back that I can't rid of and lose sleep at
night trying to convince myself are justified.

* Every time "inout" comes within a radius of a mile of what I'm doing,
it starts to stink like a skunk. I wish I could get a restraining order.
I can't instantiate "inout" variables, so writing any tests or
constraints becomes an advanced matter of defining functions and crap. I
get frustrated, I protest to this forum, and immediately a cabal is
raised under Timon's leadership. The cabal convinces me that inout is
actually great and that I'm an idiot. I do get convinced, which is more
of a proof that Timon is very good, than a testament to the conviviality
of inout. Then I leave and get back to my code, and it stinks of inout
again. And I hate it and myself for having to deal with it.

* Nobody - probably not even Timon - knows what "shared" does or is
supposed to do and not do. The most I got from Walter ever is "shared is
intentionally restricted so you can't do much without a cast". Yet the
definition of "much" and the conditions under which casting is legit are
not anywhere to be found.

* And of course, "shared" gladly partakes in qualifier combinations,
thus spreading its stink around in a combinatorial manner. Believe it or
not, the type "const inout shared T" exists. Of course, nobody knows
what it really means or how it could be used.

A "Define All Qualifiers" DIP would be a radical improvement of the
state of affairs.

Shared recently received a `-preview` which makes it really mean something; this is what shared means:
1. The data is shared; therefore, it is invalid to read or write that memory.
2. The reason this is useful as an attribute, is because you are able to attribute methods. Ability to author a set of threadsafe methods and clearly distinguish them from non-threadsafe methods is a powerful advantage over C++.

#2 isn't well accepted, although I've been pushing that for years. If #2 isn't strengthened, then it is my opinion that `shared` is completely useless, and `struct Shared(T) { private: T* sharedThing; }` is just as good.
I think shared has potential to offer critical benefit to D, and we've talked about this personally to some length. Don't kill it yet, let's try and fix it. Although it's worth recognising that if we don't fix it, then it might as well be killed as it stands today.