Yet Another Const Proposal
Sönke Ludwig
ludwig at informatik_dot_uni-luebeck.de
Fri Dec 7 07:20:42 PST 2007
(WARNING: LONG POST AHEAD)
After numerous suggested const-designs, of which none was fully
convincing, I'd like to make another suggestion.
First the latest suggestion made by Walter:
=======================
== latest proposal ==
=======================
head tail decl meaning
--------------------------------------------------------------------------------
1 mutable mutable T t t fully modifyable
2 mutable const TailConst!(T)
3 mutable invariant TailInvariant!(T)
4 const mutable --
5 const const const(T) t t not reassignable, members const
6 const invariant --
7 invariant mutable --
8 invariant const --
9 invariant invariant invariant(T) t t not reassignable, members invariant
with const T <=> const(T)
This system, while having a simple and consistent syntax, is basically also
really limited in its abilities. The important cases (2) and (3) are actually
not possible to do transparently _today_. Also (IMO) these cases are so basic
that they deserve to be possible in the core language.
Also, an important observation:
----
class C {}
struct S {
invariant(C) inst;
}
S s = S(new C);
invariant(C)* pc = &s.inst;
s = S(null);
assert( *pc != null ); // fails
// *pc is null now, despite beeing invariant(C)*, which should
// mean, that the memory pointed to by pc will never change!
----
So to make the system semantically consistent, you'd have to forbid structure
assignments, if there are any invariant members. This in turn makes the
TailInvariant!()-solution impossible without severly hacking around the type
system.
====================
== new proposal ==
====================
My solution now would use basically the same syntax, as in 2.008 - but
instead of specifying a storage class with 'const X', specify head-constness.
Transitivity still holds: if X is head const, it is also tail const:
const T <=> const const(T)
The cosmetic problem of const T not beeing equivalent to const(T) remains,
of course.
head tail decl meaning
--------------------------------------------------------------------------------
1 mutable mutable T t t fully modifyable
2 mutable const const(T) t assignable, tail const
3 mutable invariant invariant(T) t assignable, tail invariant
4 const mutable --
5 const const const T t head const, tail const
6 const invariant const invariant(T) t head const, tail invariant
7 invariant mutable --
8 invariant const --
9 invariant invariant invariant T t head invariant, tail invariant
[case (6) is there for completeness, but normally (9) would be used instead]
So this gives at least a semantically consistent system. But of course you still
have to learn the difference between 'const C' and 'const(C)'.
Some usage examples:
Class/Struct
============
S s:
s = t; // ok
s.x = 1; // ok
typeof(&s): S*
const(S) s:
s = t; // ok
s.x = 1; // error
typeof(&s): const(S)*
const S:
s = t; // error
s.x = 1; // error
typeof(&s): (const S)*
Primitives
==========
Same as structs/classes, just without the member access.
int i:
i = 1; // ok
i++; // ok
typeof(&i): int*
const(int) i:
i = 1; // ok
i++; // ok, handled as reassignment (debatable)
typeof(&i): const(int)*
const int i:
i = 1; // error
i++; // error
typeof(&i): (const int)*
Array
=====
T[] a:
a = b; // ok
a ~= t; // ok
a[0] = t; // ok
a[0].x = 1; // ok
typeof(a[0]): T
foreach( ref x; a ): typeof(x): T
const(T)[] a:
a = b; // ok
a ~= t; // ok
a[0] = t; // ok
a[0].x = 1; // error
typeof(a[0]): const(T)
foreach( ref x; a ): typeof(x): const(T)
(const T)[]:
a = b; // ok
a ~= t; // ok
a[0] = t; // error
a[0].x = 1; // error
typeof(a[0]): const T
foreach( ref x; a ): typeof(x): const T
const(T[]) a:
a = b; // ok
a ~= t; // error
a[0] = t; // error
a[0].x = 1; // error
typeof(a[0]): const T
foreach( ref x; a ): typeof(x): const T
const T[]:
a = b; // error
a ~= t; // error
a[0] = t; // error
a[0].x = 1; // error
typeof(a[0]): const T
foreach( ref x; a ): typeof(x): const T
Reference parameters
====================
These are exactly the same, as non reference parameters:
ref S s:
s = t; // ok
s.x = 1; // ok
typeof(s): S
typeof(&s): S*
ref const(S) s:
s = t; // ok
s.x = 1; // ok
typeof(s): const(S)
typeof(&s): const(S)*
ref const S s:
s = t; // ok
s.x = 1; // ok
typeof(s): const S
typeof(&s): (const S)*
Grammar
=======
Just to see if there might be any ambiguities I've also made a matching grammar.
When building a type, invariant always transitively overrides const - but not
the other way around.
TypeDecl
-> HeadConstTypeDecl
HeadConstTypeDecl
-> ConstTypeDecl
-> const NonConstTypeDecl
-> invariant NonConstTypeDecl
ConstTypeDecl
-> NonConstTypeDecl
-> const(NonConstTypeDecl)
-> invariant(NonConstTypeDecl)
NonConstTypeDecl
-> PrimitiveTypeDecl
-> PrimitiveTypeDecl[]
-> (HeadConstTypeDecl)[]
-> PrimitiveTypeDecl*
-> (HeadConstTypeDecl)*
PrimitiveTypeDecl:
-> void | int | ...
-> Identifier
Of course, some redundant declarations are possible, not sure if
this is a problem (it's acually neccessary to allow this on a semantic level
to transparently work with aliases):
const (const (const T)*)* <=> const T**
I'd be glad to hear any suggestions or mistakes I may have made.
Sönke
More information about the Digitalmars-d
mailing list