The problem with const proposals

Oskar Linde oskar.lindeREM at OVEgmail.com
Sun Dec 9 07:24:09 PST 2007


Walter Bright wrote:

> So, what are our axioms for const/invariant?
> 
> 1) Const/invariant are transitive.
> 2) Any type T can be wrapped inside a struct S, and that S can be made 
> to behave as a typedef for type T.
> 3) The contents of invariant types, once constructed, can never change.
> 4) The contents of const types, once constructed, can never be changed 
> through those const contents.
> 5) For any type T, we can construct a const(T) or an invariant(T) 
> without knowing anything about T.

Those "axioms" may a bit too specific and already locks the design into 
something sub-optimal. I think it would be better to specify design 
requirements that better show what issues need to be handled.

My orthogonal const proposal would handle most of those without problem, 
and does away with the overlapping nature of const/invariant. It 
certainly handles 1,2,3,4. 5 is fulfilled subject to my interpretation.

They key issue is separating what is constant from the access mode 
associated with pointers, references and other views.

The old "invariant" just means a view of constant data. You know the 
data is constant and are therefore able to make use of that in 
optimizations and other cases.

Separating those two aspects makes everything fall out simple and 
consistent:

== "Constness"

The keyword const would mean "constant" and the meaning is the 
established one that the compiler may rely on. Constant refers to the 
actual data (binary representation). Constant variables may not be 
changed after initialization.

= Plain old data

A constant (non-manifest versions):

const int a = 5;
const int b = runtimeFunction(); // Ok
const int c = rand(); // Ok

The constness of those are a part of their type, so typeof(a) == const(int).

A struct with constant members becomes non-assignable (but copyable):

struct S {
	const int a;
	int b;
}

The const member has to be initialized at struct initialization.

A a = S(1,2);       // Ok
A b = a;            // Ok
a = b;              // Error: assignment to a constant
a.b = b.b;          // Ok

const S c = S(1,2); // Ok
c.b = b.b;          // Error

= Pointers/Arrays

A pointer and array slice to/of constant data:

const(int) *a;
const(int) a[];

A constant pointer to an int:

int x;
const int y;

const int* a = &x;        // Ok
*x = 5;                   // Ok
a++;                      // Error: modifying a constant

const int* b = &y;        // Error: incompatible types
*b = 5;                   // Ok
const(int) *c = &y;
c++;                      // Ok
(*c)++;                   // Error: modifying a constant
const const(int) *d = &y; // A constant pointer to a constant int
d++;                      // Error: modifying a constant

= Function arguments

void foo(ref const(int) x) {
	x = 5; // Error: modifying a constant
}

The constness becomes part of the type and const(int)* is not 
convertible to int*, nor vice versa. Apart from classes, that I will 
return to later, that is about all there is to say about const.

== Access modes: Read only views

While const above refers to the modifiability of the actual data, the 
read-only part refers to the access attributes of a reference. I will 
use the keyword "in" to mean read-only below, but please substitute with 
your favorite keyword. (Such as "read", "readonly", "protected" "view", etc)

int x;
const int y;

in int * a = &x;        // Ok
*a = 5;                 // Error: No write access
in int * b = &y;        // Ok
*b = 5;                 // Error: No write access
in const(int) * c = &x; // Error: x is not constant
in const(int) * d = &y; // Ok

typeof(&x) == int *
typeof(&y) == in const(int) *

so "in const(int) *" is implicitly convertible to "in int *", but not 
the other way around.

Note that the read-only access modifier is implicit in pointers to 
constant data.

Applying the read-only property to a type means all members of the type 
are read-only:

Struct S {
	int *a;
	int b;
}

in S s;
s.b = 5;  // the read-only part doesn't refer to the actual data,
           // just the access through references
s.a = &x; // Ok;
*s.a = 5; // Error: No write access though S

An array slice behaves identical to the struct S above.

in int[] t;
t = &x[0..1];    // Ok
t[0] = 5;        // Error: No write access though S

There is a wish to make the read-only property transitive, which means 
that applying the read-only property to a type means that all types 
referred to by the type also gets the read-only property:

int *m = &x;     // Ok
in int **b = &m; // Ok
**b = 5;         // Error, since typeof(*b) == in int *m

Everything is nice and consistent so far. The remaining parts are A) 
member functions and B) classes.

== Member functions.

Member functions (methods) are called though a reference. If that 
reference only has read-only access, only such member functions are 
allowed to be called. No changes here. Transitivity of the read-only 
propert will have to be handled manually. Something like:

struct Array(T) {
	T[] arr;
	accessof!(this) T get()(int i) { return arr[i]; }

	static if (!is(T == const)) {
		void set(int i, ref T v) { arr[i] = v; }
	}
}

Array!(int);
Array!(int *);
Array!(const int); // <- This is the "invariant" case

== Classes

Since classes don't have a value type, it is not possible to define 
constant classes. I have two views on this. You could come up with a 
syntax that makes it possible to declare a type that is a reference to a 
constant class, but it seems to me that most classes that are meant to 
be constant have to be designed specifically for this purpose, so I 
propose making the constant a part of the class declaration:

const class String {
	this(char[] data) {...}
	...
}

For classes that support both a modifiable and a constant mode, and the 
constant mode is important enough to let the compiler know about, one 
could define:

const class ConstString : MutableString {
	// any non-read-only method of the superclass
         // is blocked
	...
}

Classes are special beasts that have other was to do access control (a 
read only view interface for instance).

To end, I'd be very interested in hearing what the issues with this 
design are and what unspoken design requirements this fails to fulfill.

-- 
Oskar



More information about the Digitalmars-d mailing list