Proposal - Revised Syntax for const and final

Daniel Keep daniel.keep.lists at gmail.com
Sat Sep 8 07:01:04 PDT 2007


Having gone over your proposal a few times, I have to admit that I don't
like it much.  Sorry.

I just want to say, that this is intended to be constructive criticism,
not bashing. :)

Janice Caron wrote:
> I've had a little think about the whole const syntax, and I've come up
> with the following proposal. I don't know how well it will be
> received, because it's different from what D2.0 is now, and so will
> break 2.0 code if implemented. On the other hand, I don't think that's
> a bad thing, because I think the benefits outweight that.

Well, whether that's true is debatable, but D 2.0 is still very, *very*
alpha; sudden, drastic changes to the syntax are perfectly fine, so long
as they happen *now* rather than later. :)

> So, there is a rationale behind these choices. The philosophy is that
> the same syntax should apply to all types, regardless of whether they
> are value types or reference types. I also believe in the principle of
> least surprise. That means, even though there are lots of different
> things that need to be achieved, there should be a single, consistent
> approach which applies to all of them. Where syntax needs to be
> extended, it should be done in a way which is analogous to some
> existing D syntax.

I agree with your intentions here; having a simpler syntax would be
great, and consistency[1] is always nice.

> [snip]
> 
> TOTAL constness --
> In general, if a variable x is declared to be of type const(T)
> then x is const, and x.member is const (and x.member.member, etc.)

That's called "transitivity".

> [snip]
>
> HEAD constness --
> In general, if a variable x is declared to be of type final(T)
> then x is const, but x.member is mutable (and x.member.member, etc.)
> 
> final(C) c;
> c = new C();            /* Error */
> c.x = 1;                /* OK* /
> c.p = new int[1];       /* OK */
> c.p[0] = 1;             /* OK */

That's fine.

> final(S) s;
> s = S();                /* Error */
> s.x = 1;                /* OK* /
> s.p = new int[1];       /* OK */
> s.p[0] = 1;             /* OK */

This is just silly.  You're completely ignoring how structures work.

auto y = &(s.x); // typeof(y) == int*
*(cast(S*)y) = S();

I just bypassed the head constness of s.  Yes, I casted to do it, but
the point was that I *never* cast away the const-ness, because it was
never there.  Even then, I could do the same thing with foreach and
s.tupleof, and you wouldn't even need a cast to do it.

You *cannot* have a structure const and not have its members const; it
doesn't make sense.  You can either change it, or you can't.

> final(cdouble) z;
> z = 1;                  /* Error */
> z.re = 1;               /* OK */
> z.im = 1;               /* OK */

As above.

> final(int[][]) a;       /* a is const array of array of int */
> a.length = 1;           /* Error */
> a[0].length = 1;        /* OK */
> a[0][0] = 1;            /* OK */

Now you're contradicting yourself.  You said that if something is final,
you can't change *it*, but you *can* change it's members.  An array is
basically this:

struct Array
{
    void* ptr;
    size_t length;
}

Yet now you're saying that you can't change length.

Assuming this is a typo, it still doesn't work.  If you change length to
be larger than the allocated capacity of the array, then D will
reallocate it and will effectively do this[2]:

a = new int[][](new_length);

Again, you can't have something be both mutable and not mutable at the
same time.

> [snip]
> 
> TAIL constness --
> In general, if a variable x is declared to be of type const*(T)
> then x is mutable, but x.member is const (and x.member.member, etc.)
> 
> const*(C) c;
> c = new C();             /* OK* /
> c.x = 1;                 /* Error */
> c.p = new int[1];        /* Error */
> c.p[0] = 1;              /* Error */

So this is basically the *current* behaviour of const.

> const*(S) s;
> s = S();                 /* OK* /
> s.x = 1;                 /* Error */
> s.p = new int[1];        /* Error */
> s.p[0] = 1;              /* Error */

s = S(1, [1].dup);

I just bypassed the const*-ness.

> const*(cdouble) z;
> z = 1;                  /* OK* /
> z.re = 1;               /* Error */
> z.im = 1;               /* Error */
> 
> const*(int[][]) a;      /* a is array of const array of const int */
> a.length = 1;           /* OK */
> a[0].length = 1;        /* Error */
> a[0][0] = 1;            /* Error */

Isn't this just const(int[])[]?  It makes me uneasy that there's a whole
set of synonyms for a particular const type.

> [snip]

Honestly, I appreciate why you're doing this.  The less rules and mental
exceptions you need to keep track of, the better.  But D is a systems
language; that means you need to understand what is actually going on.

If we went and removed structs entirely from D, then this would actually
be a pretty cool way of doing things.  But since we do have POD types, I
don't think it's going to work.

Also, you've said nothing on invariant.  I assume you mean to treat
invariant in the same way as const?

	-- Daniel

[1] That said, you have to be careful with consistency.  You can easily
take it to absurd extremes if you're not careful.

[2] It also copies the old data across, but then the statement would
have gotten ugly. :)



More information about the Digitalmars-d mailing list