Thread overview
Casting const/invariant
Jan 02, 2008
Jason House
Jan 03, 2008
Daniel919
Jan 03, 2008
Jason House
Jan 03, 2008
Janice Caron
Jan 03, 2008
Daniel919
January 02, 2008
I don't know about others, but it looks to me like the community has come close to accepting D's const design.  I know it's probably the last thing anyone wants to hear, but below are my thoughts on extending the design.

Right now, the docs say "[When casting data to invariant] it is up to the programmer to ensure that no other mutable references to the same data exist. "

Const is useful for interface guarantees, but does not allow the compiler to optimize code like it can with invariant data.  Unlike const, the compiler will be unwilling to (silently) cast data to invariant.  For invariant data to be useful, I think there needs to be a way for casting to be safe.

I think the key is adding a concept of exclusive write access to data.  For the sake of this post, I'll call data with exclusive write access "exclusive" and those that don't have it "shared".  Just to confuse everyone, I'll use "readonly" instead of const in my discussion...

Note that with this notation,
  invariant = exclusive readonly
  const = shared readonly

shared data gives behavior exactly like everyone currently expects.  You never know who has a mutable reference to the data, and the compiler gives no guarantees to that fact.

exclusive data can be implicitly cast to scope invariant for function calls.  This would allow a function with const parameter signature to use a version optimized for invariant access.

I would suggest that member/global variables default to shared access and local variables (in functions) default to exclusive access.  Using the variables as "in" parameters or "scope ref" parameters would require no modification.  Using them inside delegates or passing to non-scope "ref" parameters would require the data be shared.

Function return types and out parameters to functions are the stickiest part.  By default, they'd have to be shared access, but for this scheme to really be worthwhile, some framework for specifying when these outputs can be used as exclusive access (AKA the function leaves no mutable references in any other locations)

Thoughts?
January 03, 2008
> Right now, the docs say "[When casting data to invariant] it is up to the programmer to ensure that no other mutable references to the same data exist. "
When creating a new invariant struct/class, the compiler can always infer a cast(invariant).
invariant clFoo foo = /*cast(invariant)*/ new clFoo();

> Const is useful for interface guarantees, but does not allow the compiler to optimize code like it can with invariant data.  Unlike const, the compiler will be unwilling to (silently) cast data to invariant.  For invariant data to be useful, I think there needs to be a way for casting to be safe.
Correct, const is only logical, not physical, as it is now and therefore has to be treated like mutable.

> I think the key is adding a concept of exclusive write access to data.  For the sake of this post, I'll call data with exclusive write access "exclusive" and those that don't have it "shared".  Just to confuse everyone, I'll use "readonly" instead of const in my discussion...
I recommend the use of readonly instead of const. See my post "const=readonly invariant=const".

> Note that with this notation,
>   invariant = exclusive readonly
>   const = shared readonly
> 
> shared data gives behavior exactly like everyone currently expects.  You never know who has a mutable reference to the data, and the compiler gives no guarantees to that fact.
> 
> exclusive data can be implicitly cast to scope invariant for function calls.  This would allow a function with const parameter signature to use a version optimized for invariant access.
> 
> I would suggest that member/global variables default to shared access and local variables (in functions) default to exclusive access.  Using the variables as "in" parameters or "scope ref" parameters would require no modification.  Using them inside delegates or passing to non-scope "ref" parameters would require the data be shared.
> 
> Function return types and out parameters to functions are the stickiest part.  By default, they'd have to be shared access, but for this scheme to really be worthwhile, some framework for specifying when these outputs can be used as exclusive access (AKA the function leaves no mutable references in any other locations)
> 
> Thoughts?
In my ideas, const is the keyword that is mostly used. Therefore the compiler would not have to silently try to convert
"const int x = 1;" to what it actually means: "invariant int x = 1;"

readonly would mean what you call "shared readonly"
and const what you call "exclusive readonly"

readonly data in general has to be treated like mutable data
For optimization the compiler might generate two versions (of a function):
one for the case that the data is mutable
another for the case that the data is const

void foo(readonly Foo foo) { return foo.x; }
generates two versions, that are overloadable
void foo(/*mutable*/ Foo foo) { return foo.x; }
void foo(const Foo foo) { return foo.x; } //can be optimized
January 03, 2008
Daniel919 wrote:
> I recommend the use of readonly instead of const. See my post "const=readonly invariant=const".

Maybe using that in my post was a mistake.  I certainly don't intend to start a syntax war.  Since I was mucking with stuff, I figured using readonly would make stuff simpler.  Turns out that I hardly used the term and probably could have removed it from the post.


> readonly data in general has to be treated like mutable data
> For optimization the compiler might generate two versions (of a function):
> one for the case that the data is mutable
> another for the case that the data is const

One step at a time ;)  I'd love to see an endstate with const where the compiler knows how to handle/optimize const stuff with as little input from the programmer as possible.
January 03, 2008
On 1/3/08, Daniel919 <Daniel919@web.de> wrote:
> When creating a new invariant struct/class, the compiler can always
> infer a cast(invariant).
> invariant clFoo foo = /*cast(invariant)*/ new clFoo();

I don't believe that is true. Consider

    class C
    {
        int *p;
        this() { p = &some_global_variable; }
    }

I don't see anything to stop *C.p from being modified by another means.
January 03, 2008
Janice Caron wrote:
> On 1/3/08, Daniel919 <Daniel919@web.de> wrote:
>> When creating a new invariant struct/class, the compiler can always
>> infer a cast(invariant).
>> invariant clFoo foo = /*cast(invariant)*/ new clFoo();
> 
> I don't believe that is true. Consider
> 
>     class C
>     {
>         int *p;
>         this() { p = &some_global_variable; }
>     }
> 
> I don't see anything to stop *C.p from being modified by another means.

import std.stdio;

int some_global_variable = 1;

/*invariant*/ class C
{
	int *p;
	this() { p = &some_global_variable; }
}

void main()
{
	invariant C c = /*cast(invariant)*/ new C();
//Error: cannot implicitly convert expression (& some_global_variable) of type int* to invariant(int*)

	invariant C c = cast(invariant) new C();
//OK, you promise not to change some_global_variable anymore

	writefln( *c.p );
	some_global_variable = 2;
//undefined behavior, you lied
	writefln( *c.p );
}


The inferred cast(invariant) would mean the same as
declaring the struct/class "invariant" for this new object.
So it would ensure that everything is deep invariant.

An explicit cast(invariant) on an existing reference is different.
There might be pointers/refs that point to mutable data. But we
ensure that this data will not change anymore.