www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - const and immutable objects

reply Graham St Jack <graham.stjack internode.on.net> writes:
I have been trying (again) to start using const and immutable objects in 
a project, and found I kept getting stuck with the need to change the 
value of object references, which isn't allowed. I don't quite follow the 
arguments that mean that this has to be the case, but I'm prepared to 
accept them.

After some experimenting with Andrei's Rebindable template, I still 
couldn't get past the problems. For a start, the get method is private so 
I couldn't test for null.

In the end I cooked up the following, which is a very simple workaround 
that just lets me assign to a const or immutable object reference. It 
works just fine, and can be used in templates like containers without 
forcing the container to care about what it is containing.

Comments?

//
// Assign dest to src, using brute-force methods as necessary if T
// is an immutable or const object.
//
// The intent is to avoid needing routine nasty tricks in
// application code, especially in template code which should be
// spared the complication of handling this very annoying
// restriction on objects. Use of this template function doesn't
// let you change the object itself, just the reference to it - so
// all should be well.
//
void forcefulAssign(T)(ref T dest, T src) {
    static if (!is(T X == const(U), U) &&  !is(T X == invariant(U), U)) {
        // T is not const or immutable - just assign normally
        dest = src;
    }
    else static if (is(T : Object)) {
        // T is a const or immutable object - use brute force
        U * ptr = cast(U *) &dest;
        *ptr = cast(U) src;
    }
    else {
        // T is some other const or immutable type - not assignable
        static assert(false, "cannot assign to " ~ T.stringof);
    }
}

// assign the default initializer to something
void forcefulInitialize(T)(ref T dest) {
    static if (!is(T X == const(U), U) &&  !is(T X == invariant(U), U)) {
        // T is not const or immutable - just initialise normally
        dest = T.init;
    }
    else static if (is(T : Object)) {
        // T is a const or immutable object - use brute force
        U * ptr = cast(U *) &dest;
        *ptr = U.init;
    }
    else {
        // T is some other const or immutable type - not assignable
        static assert(false, "cannot assign to " ~ T.stringof);
    }
}
Aug 30 2009
next sibling parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
Graham St Jack Wrote:

 I have been trying (again) to start using const and immutable objects in 
 a project, and found I kept getting stuck with the need to change the 
 value of object references, which isn't allowed. I don't quite follow the 
 arguments that mean that this has to be the case, but I'm prepared to 
 accept them.
 
 After some experimenting with Andrei's Rebindable template, I still 
 couldn't get past the problems. For a start, the get method is private so 
 I couldn't test for null.
 
 In the end I cooked up the following, which is a very simple workaround 
 that just lets me assign to a const or immutable object reference. It 
 works just fine, and can be used in templates like containers without 
 forcing the container to care about what it is containing.
 
 Comments?
 
 //
 // Assign dest to src, using brute-force methods as necessary if T
 // is an immutable or const object.
 //
 // The intent is to avoid needing routine nasty tricks in
 // application code, especially in template code which should be
 // spared the complication of handling this very annoying
 // restriction on objects. Use of this template function doesn't
 // let you change the object itself, just the reference to it - so
 // all should be well.
 //
 void forcefulAssign(T)(ref T dest, T src) {
     static if (!is(T X == const(U), U) &&  !is(T X == invariant(U), U)) {
         // T is not const or immutable - just assign normally
         dest = src;
     }
     else static if (is(T : Object)) {
         // T is a const or immutable object - use brute force
         U * ptr = cast(U *) &dest;
         *ptr = cast(U) src;
     }
     else {
         // T is some other const or immutable type - not assignable
         static assert(false, "cannot assign to " ~ T.stringof);
     }
 }
 
 // assign the default initializer to something
 void forcefulInitialize(T)(ref T dest) {
     static if (!is(T X == const(U), U) &&  !is(T X == invariant(U), U)) {
         // T is not const or immutable - just initialise normally
         dest = T.init;
     }
     else static if (is(T : Object)) {
         // T is a const or immutable object - use brute force
         U * ptr = cast(U *) &dest;
         *ptr = U.init;
     }
     else {
         // T is some other const or immutable type - not assignable
         static assert(false, "cannot assign to " ~ T.stringof);
     }
 }
 
I agree that D lacks a mechanism to separate the object from it's reference. Maybe syntax like the following could be used to apply the storage class to the object value, and not the reference value: class Foo; void bar(in Foo& foo) {} It's quite ugly and C-like, but that's the first thing that came to mind. The reference value is unique to the current method and shouldn't share the same storage qualifiers as it's referenced memory.
Aug 30 2009
parent reply Graham St Jack <Graham.StJack internode.on.net> writes:
On Sun, 30 Aug 2009 21:28:18 -0400, Jeremie Pelletier wrote:


 I agree that D lacks a mechanism to separate the object from it's
 reference. Maybe syntax like the following could be used to apply the
 storage class to the object value, and not the reference value:
 
 class Foo;
 void bar(in Foo& foo) {}
 
 It's quite ugly and C-like, but that's the first thing that came to
 mind. The reference value is unique to the current method and shouldn't
 share the same storage qualifiers as it's referenced memory.
I think the time for pining over this particular syntax feature of D is over - as nice as it would be to be able to fix the problem in the language, it would be too disruptive right now. What we need is something in phobos that works around the problem, and Rebindable isn't suitable as it stands because: * It presents different interfaces for different kinds of wrapped types. * It doesn't let you access the wrapped object except via opDot. My suggested workaround is perhaps a bit rough, but it does work. I would like to hear from anyone who is using const and immutable objects in D2 who has something better, or has suggestions to improve my code.
Aug 30 2009
parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
Graham St Jack Wrote:

 On Sun, 30 Aug 2009 21:28:18 -0400, Jeremie Pelletier wrote:
 
 
 I agree that D lacks a mechanism to separate the object from it's
 reference. Maybe syntax like the following could be used to apply the
 storage class to the object value, and not the reference value:
 
 class Foo;
 void bar(in Foo& foo) {}
 
 It's quite ugly and C-like, but that's the first thing that came to
 mind. The reference value is unique to the current method and shouldn't
 share the same storage qualifiers as it's referenced memory.
I think the time for pining over this particular syntax feature of D is over - as nice as it would be to be able to fix the problem in the language, it would be too disruptive right now.
I think it would be better to fix it in D2 instead of patching a language design flaw with a template. It wouldn't be the first time D2 has changes that breaks existing code anyways (and the upcoming T[new] will definitely break a lot of code, so does shared when it works, etc), so it would be less disruptive to do it now than never do it.
Aug 31 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Jeremie Pelletier wrote:
 Graham St Jack Wrote:
 
 On Sun, 30 Aug 2009 21:28:18 -0400, Jeremie Pelletier wrote:
 
 
 I agree that D lacks a mechanism to separate the object from it's
  reference. Maybe syntax like the following could be used to
 apply the storage class to the object value, and not the
 reference value:
 
 class Foo; void bar(in Foo& foo) {}
 
 It's quite ugly and C-like, but that's the first thing that came
 to mind. The reference value is unique to the current method and
 shouldn't share the same storage qualifiers as it's referenced
 memory.
I think the time for pining over this particular syntax feature of D is over - as nice as it would be to be able to fix the problem in the language, it would be too disruptive right now.
I think it would be better to fix it in D2 instead of patching a language design flaw with a template. It wouldn't be the first time D2 has changes that breaks existing code anyways (and the upcoming T[new] will definitely break a lot of code, so does shared when it works, etc), so it would be less disruptive to do it now than never do it.
I agree with the sentiment. The issue is, however, that the change adds a fair amount of complexity to the const system for an arguably not-often-used need. Andrei
Aug 31 2009
next sibling parent Jeremie Pelletier <jeremiep gmail.com> writes:
Andrei Alexandrescu Wrote:

 Jeremie Pelletier wrote:
 Graham St Jack Wrote:
 
 On Sun, 30 Aug 2009 21:28:18 -0400, Jeremie Pelletier wrote:
 
 
 I agree that D lacks a mechanism to separate the object from it's
  reference. Maybe syntax like the following could be used to
 apply the storage class to the object value, and not the
 reference value:
 
 class Foo; void bar(in Foo& foo) {}
 
 It's quite ugly and C-like, but that's the first thing that came
 to mind. The reference value is unique to the current method and
 shouldn't share the same storage qualifiers as it's referenced
 memory.
I think the time for pining over this particular syntax feature of D is over - as nice as it would be to be able to fix the problem in the language, it would be too disruptive right now.
I think it would be better to fix it in D2 instead of patching a language design flaw with a template. It wouldn't be the first time D2 has changes that breaks existing code anyways (and the upcoming T[new] will definitely break a lot of code, so does shared when it works, etc), so it would be less disruptive to do it now than never do it.
I agree with the sentiment. The issue is, however, that the change adds a fair amount of complexity to the const system for an arguably not-often-used need. Andrei
Correct me if I'm wrong, but I don't see how complex that could be, since the compiler already needs to make the difference between the reference value and the object being referenced. The only thing that would need change is how that view is reflected in the syntax. How about a .ref property? class A {} void foo(in A a) { }
Aug 31 2009
prev sibling parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
Andrei Alexandrescu Wrote:

 Jeremie Pelletier wrote:
 Graham St Jack Wrote:
 
 On Sun, 30 Aug 2009 21:28:18 -0400, Jeremie Pelletier wrote:
 
 
 I agree that D lacks a mechanism to separate the object from it's
  reference. Maybe syntax like the following could be used to
 apply the storage class to the object value, and not the
 reference value:
 
 class Foo; void bar(in Foo& foo) {}
 
 It's quite ugly and C-like, but that's the first thing that came
 to mind. The reference value is unique to the current method and
 shouldn't share the same storage qualifiers as it's referenced
 memory.
I think the time for pining over this particular syntax feature of D is over - as nice as it would be to be able to fix the problem in the language, it would be too disruptive right now.
I think it would be better to fix it in D2 instead of patching a language design flaw with a template. It wouldn't be the first time D2 has changes that breaks existing code anyways (and the upcoming T[new] will definitely break a lot of code, so does shared when it works, etc), so it would be less disruptive to do it now than never do it.
I agree with the sentiment. The issue is, however, that the change adds a fair amount of complexity to the const system for an arguably not-often-used need. Andrei
Correct me if I'm wrong, but I don't see how complex that could be. I assume the compiler already makes the difference between the reference value and the referenced object, all that needs to be done is to reflect that view in the syntax. Maybe a .ref property? class A {} void foo(in A a) { a = new A(); // fail, cannot modify const a.ref = new A(); // ok, only reference value is modified } If you want to make the reference immutable, you can use the .ref on the class identifier: const(A.ref)[] AList; // slice of const references to A It may not be perfect, but I do firmly believe that the D syntax needs a mechanism to separate references from objects, even if rarely used, I can think of at least 15-20 places in my code I had to use workarounds for such cases. I don't think A* should be used since that is a pointer to an object reference, it would break too much code and be incredibly hard to track back. A& looks nice because that's how references are already handled in C++, and we'd get const(A)& the same way we get const(int)*.
Aug 31 2009
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Jeremie Pelletier wrote:
 Andrei Alexandrescu Wrote:
 
 Jeremie Pelletier wrote:
 Graham St Jack Wrote:

 On Sun, 30 Aug 2009 21:28:18 -0400, Jeremie Pelletier wrote:


 I agree that D lacks a mechanism to separate the object from it's
  reference. Maybe syntax like the following could be used to
 apply the storage class to the object value, and not the
 reference value:

 class Foo; void bar(in Foo& foo) {}

 It's quite ugly and C-like, but that's the first thing that came
 to mind. The reference value is unique to the current method and
 shouldn't share the same storage qualifiers as it's referenced
 memory.
I think the time for pining over this particular syntax feature of D is over - as nice as it would be to be able to fix the problem in the language, it would be too disruptive right now.
I think it would be better to fix it in D2 instead of patching a language design flaw with a template. It wouldn't be the first time D2 has changes that breaks existing code anyways (and the upcoming T[new] will definitely break a lot of code, so does shared when it works, etc), so it would be less disruptive to do it now than never do it.
I agree with the sentiment. The issue is, however, that the change adds a fair amount of complexity to the const system for an arguably not-often-used need. Andrei
Correct me if I'm wrong, but I don't see how complex that could be. I assume the compiler already makes the difference between the reference value and the referenced object, all that needs to be done is to reflect that view in the syntax.
There was a lot of discussion around "head constness" and "tail constness" a while ago in this newsgroup. The general sentiment was that adding one or both of these nuances would make const too difficult to bother with.
 Maybe a .ref property?
 
 class A {}
 void foo(in A a) {
     a = new A(); // fail, cannot modify const
     a.ref = new A(); // ok, only reference value is modified
 }
 
 If you want to make the reference immutable, you can use the .ref on the class
identifier:
 
 const(A.ref)[] AList; // slice of const references to A
 
 It may not be perfect, but I do firmly believe that the D syntax needs a
mechanism to separate references from objects, even if rarely used, I can think
of at least 15-20 places in my code I had to use workarounds for such cases.
 
 I don't think A* should be used since that is a pointer to an object
reference, it would break too much code and be incredibly hard to track back.
A& looks nice because that's how references are already handled in C++, and
we'd  get const(A)& the same way we get const(int)*.
There would still be a problem with struct objects. Andrei
Aug 31 2009
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sun, 30 Aug 2009 18:31:33 -0400, Graham St Jack  
<graham.stjack internode.on.net> wrote:

 I have been trying (again) to start using const and immutable objects in
 a project, and found I kept getting stuck with the need to change the
 value of object references, which isn't allowed. I don't quite follow the
 arguments that mean that this has to be the case, but I'm prepared to
 accept them.

 After some experimenting with Andrei's Rebindable template, I still
 couldn't get past the problems. For a start, the get method is private so
 I couldn't test for null.

 In the end I cooked up the following, which is a very simple workaround
 that just lets me assign to a const or immutable object reference. It
 works just fine, and can be used in templates like containers without
 forcing the container to care about what it is containing.

 Comments?
First, a little history lesson. An earlier version of const had this distinction: class C {} const(C) c; // c is rebindable, what it points to is const const C c2; // c2 is not rebindable. The const(C) format was modeled after pointers and arrays, i.e. const(C)[] and const(C)*, but since the "reference" is not explicitly stated, it's invisible in the const format also :) So that was thrown out because it was determined that const(...) should mean that anything inside the parentheses should be const (including a class reference). What followed was several attempts (including some by me) to come up with a way to denote a tail-const class reference. Most of them centered around "pulling out" the reference part, i.e. ref const(C) or const(*C)*. Others included new keywords. In the end, none of them looked great, and none of them made Walter change his mind. So via syntax, there is no way to say "rebindable reference to const class data." So to answer your first question, there's no objection by Walter and crew as to being able to rebind a const class reference, there's just no good syntax to denote it (that has been presented so far anyways). Then Andrei came along with Rebindable, and the argument died down. I had a feeling that Rebindable would be somewhat unusable in its current form, but given that we were about to get opImplicitCast and other niceties, it seemed Rebindable would be sufficient in the future. OK, so now we have alias this which is supposed to be the implementation of opImplicitCast, and you say it's still too difficult. Looking at the source, it looks like Rebindable needs some attention, as it still doesn't use alias this. I have no idea if there is a way to implement !is null, but I assume that it should be forwarded to the alias this member. So I think a bug report is in order. Rebindable should not use opDot, but rather alias this. Implicit casting is paramount to make Rebindable look like a transparent wrapper. -Steve
Aug 31 2009
parent Graham St Jack <Graham.StJack internode.on.net> writes:
On Mon, 31 Aug 2009 09:12:33 -0400, Steven Schveighoffer wrote:

 On Sun, 30 Aug 2009 18:31:33 -0400, Graham St Jack
 <graham.stjack internode.on.net> wrote:
 
 I have been trying (again) to start using const and immutable objects
 in a project, and found I kept getting stuck with the need to change
 the value of object references, which isn't allowed. I don't quite
 follow the arguments that mean that this has to be the case, but I'm
 prepared to accept them.

 After some experimenting with Andrei's Rebindable template, I still
 couldn't get past the problems. For a start, the get method is private
 so I couldn't test for null.

 In the end I cooked up the following, which is a very simple workaround
 that just lets me assign to a const or immutable object reference. It
 works just fine, and can be used in templates like containers without
 forcing the container to care about what it is containing.

 Comments?
First, a little history lesson. An earlier version of const had this distinction: class C {} const(C) c; // c is rebindable, what it points to is const const C c2; // c2 is not rebindable. The const(C) format was modeled after pointers and arrays, i.e. const(C)[] and const(C)*, but since the "reference" is not explicitly stated, it's invisible in the const format also :) So that was thrown out because it was determined that const(...) should mean that anything inside the parentheses should be const (including a class reference). What followed was several attempts (including some by me) to come up with a way to denote a tail-const class reference. Most of them centered around "pulling out" the reference part, i.e. ref const(C) or const(*C)*. Others included new keywords. In the end, none of them looked great, and none of them made Walter change his mind. So via syntax, there is no way to say "rebindable reference to const class data." So to answer your first question, there's no objection by Walter and crew as to being able to rebind a const class reference, there's just no good syntax to denote it (that has been presented so far anyways). Then Andrei came along with Rebindable, and the argument died down. I had a feeling that Rebindable would be somewhat unusable in its current form, but given that we were about to get opImplicitCast and other niceties, it seemed Rebindable would be sufficient in the future. OK, so now we have alias this which is supposed to be the implementation of opImplicitCast, and you say it's still too difficult. Looking at the source, it looks like Rebindable needs some attention, as it still doesn't use alias this. I have no idea if there is a way to implement !is null, but I assume that it should be forwarded to the alias this member. So I think a bug report is in order. Rebindable should not use opDot, but rather alias this. Implicit casting is paramount to make Rebindable look like a transparent wrapper. -Steve
I remember the history - it was long and complicated, with no answer that looked good. I also agree that Rebindable as it stands needs work, and definitely needs to be a transparent wrapper. Since I don't understand the issues all that well, how about you submit the bug report?
Aug 31 2009
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Graham St Jack wrote:
 I have been trying (again) to start using const and immutable objects in 
 a project, and found I kept getting stuck with the need to change the 
 value of object references, which isn't allowed. I don't quite follow the 
 arguments that mean that this has to be the case, but I'm prepared to 
 accept them.
 
 After some experimenting with Andrei's Rebindable template, I still 
 couldn't get past the problems. For a start, the get method is private so 
 I couldn't test for null.
 
 In the end I cooked up the following, which is a very simple workaround 
 that just lets me assign to a const or immutable object reference. It 
 works just fine, and can be used in templates like containers without 
 forcing the container to care about what it is containing.
 
 Comments?
 
[snip code] Hi Graham, Could you please post a short snippet (or a few) that illustrate the problems you ran into with Rebindable? Thanks, Andrei
Aug 31 2009
parent Graham St Jack <Graham.StJack internode.on.net> writes:
On Mon, 31 Aug 2009 15:20:21 -0500, Andrei Alexandrescu wrote:

 Graham St Jack wrote:
 I have been trying (again) to start using const and immutable objects
 in a project, and found I kept getting stuck with the need to change
 the value of object references, which isn't allowed. I don't quite
 follow the arguments that mean that this has to be the case, but I'm
 prepared to accept them.
 
 After some experimenting with Andrei's Rebindable template, I still
 couldn't get past the problems. For a start, the get method is private
 so I couldn't test for null.
 
 In the end I cooked up the following, which is a very simple workaround
 that just lets me assign to a const or immutable object reference. It
 works just fine, and can be used in templates like containers without
 forcing the container to care about what it is containing.
 
 Comments?
 
[snip code] Hi Graham, Could you please post a short snippet (or a few) that illustrate the problems you ran into with Rebindable? Thanks, Andrei
The code fragments are lost in the sands of time - I can recreate them if necessary, but here is an outline of what the problems were: Rebindable!(Foo) foo = null; foo = cast(immutable) new Foo(); // ok so far foo = Foo.init; // still ok if (foo !is null) {} // compiler error if (foo.get !is null) {} // another compiler error, and nasty! The main thing that has to change in Rebindable is that it needs to be a transparent wrapper, so that "if (foo !is null) {}" works. What I was doing to get into trouble was writing some communications code using Fibers, and I wanted the message objects to be immutable (call me silly, but I wanted to give it a go). This meant I needed to: * Have a templated queue class that could contain any sort of mutable types, and const or immutable objects. In particular, I didn't want the queue to have to use different code for objects and (say) integers. * Pass messages between the Fibre's stack frame and the main thread, so for an immutable object reference I needed to: test for null, assign to null and assign to a new message. The motivation for passing immutable messages around is that there is an implicit assumption that these messages don't mutate as they fan out to various destinations, and I wanted some compiler enforcement.
Sep 01 2009