Proposal - Revised Syntax for const and final

Janice Caron caron800 at googlemail.com
Sat Sep 8 04:57:25 PDT 2007


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.

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.

My proposal involves ditching altogether the const "storage class",
and a redefinition of the usages of const and final.

Specifically: (1) final would now use the bracket syntax, so that
final-ness can be applied to the nth level of indirection, not just
the top level, (2) const now implies total constness, and (3) the
symbol * placed immediately after const implies that the constness
applies one level of indirection down. This is best shown by example.


/* Definitions */

class C
{
    this() {p.length=1}
    int x;
    int p[];
}

struct S
{
    static opCall() { S s; s.p.length=1; return s; }
    int x;
    int p[];
}



Without const, everything works as normal

C c;
c = new C();           /* OK */
c.x = 1;               /* OK* /
c.p = new int[1];      /* OK */
c.p[0] = 1;            /* OK */

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

cdouble z;
z = 1;                 /* OK */
z.re = 1;              /* OK */
z.im = 1;              /* OK */

int[][] a;
a.length = 1;           /* OK */
a[0].length = 1;        /* OK */
a[0][0] = 1;            /* OK */



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

const(C) c;
c = new C();            /* Error */
c.x = 1;                /* Error */
c.p = new int[1];       /* Error */
c.p[0] = 1;             /* Error */

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

const(cdouble) z;
z = 1;                   /* Error */
z.re = 1;                /* Error */
z.im = 1;                /* Error */

const(int[][]) a;        /* a is const array of const array of const int */
a.length = 1;            /* Error */
a[0].length = 1;         /* Error */
a[0][0] = 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 */

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



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 */

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


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

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 */

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

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



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 */

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

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 */

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

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

Of course, const*(int) is meaningless, since int has no
assignable properties. That's why everything is allowed in the
last example.

But there's one thing I've missed out...

Walter wants to make it possible to to specify that the immediate
members of a struct, but not the struct itself, be const*.
You can't do that in C++, and it's the reason C++ has two different
types, iterator and const_iterator (not const iterator).

This omission seems to be the reason for the weird existing syntax
in D2.0. It's the reason reference types behave differently from
non-reference types. It's the reason that const(S) for structs does
what it does in D2.0. It seems to be very important to Walter that
that be allows. However, my notation extends naturally to cover this
circumstance. Thus:

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


I considered whether or not the * notation should also be applicable
to final. I came to the conclusion, probably not. Though it is
logical, it is also pointless. Again, this is best shown by example:

final*(int[][]) a;      /* pointless, so lets disallow */
final(int[])[] a;       /* better way of achieving the same thing */



More information about the Digitalmars-d mailing list