www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Casting const/invariant

reply Jason House <jason.james.house gmail.com> writes:
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?
Jan 02 2008
parent reply Daniel919 <Daniel919 web.de> writes:
 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
Jan 02 2008
next sibling parent Jason House <jason.james.house gmail.com> writes:
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.
Jan 02 2008
prev sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
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.
Jan 02 2008
parent Daniel919 <Daniel919 web.de> writes:
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.
Jan 03 2008