Thread overview
Overzealous immutable and classes
Jul 16, 2010
Gareth Charnock
Jul 16, 2010
Jonathan M Davis
Jul 16, 2010
Gareth Charnock
Jul 16, 2010
bearophile
July 16, 2010
So having got a collectors' edition TDPL, I though I'd have a try at writing some concurrent code. The idea was a worker thread(s) would do some work and write the results to some immutable objects. These would get passed to an indexer thread that would do neat stuff like indexing the references to the objects in things like hash tables and such, performing any reduce type operations along the way. These views would then be passed back to workers in due course. Unfortunately I hit a snag. It appears you can't do this:

class A {
   ...
}
immutable A a1 = new immutable(A);  //pointer to a real, immutable object
immutable A a2;  //pointer to null
a2 = a1;   //error, guess I doomed a2 to a life of being useless

I thought that as such assignments don't change the underlying objects in memory, they would be fine. Compare to:

immutable(char)[] s1 = "hello world"; //fat pointer to a section of immutable memory
immutable(char)[] s2;  //fat pointer to null
s2 = s1; //fine

The only solutions I can think of:
1) Use a pointer to a class. Works, but that just seems very unsafe and just plain un-D-ish. We also have to dereference two pointers.
2) Work entirely with immutable(A)[], which is not quite as crazy as it seems as we are copying about arrays of pointers rather than arrays of A. It's still quite crazy.
3) Write some sort of wrapper struct to hide the pointer.
4) Perhaps ref works like C++ int&?
ref immutable(A) ref_a //error
Nope.

So is this intended behavior? Am I missing something obvious?

July 16, 2010
On Thursday, July 15, 2010 17:40:26 Gareth Charnock wrote:
> So having got a collectors' edition TDPL, I though I'd have a try at writing some concurrent code. The idea was a worker thread(s) would do some work and write the results to some immutable objects. These would get passed to an indexer thread that would do neat stuff like indexing the references to the objects in things like hash tables and such, performing any reduce type operations along the way. These views would then be passed back to workers in due course. Unfortunately I hit a snag. It appears you can't do this:
> 
> class A {
>     ...
> }
> immutable A a1 = new immutable(A);  //pointer to a real, immutable object
> immutable A a2;  //pointer to null
> a2 = a1;   //error, guess I doomed a2 to a life of being useless
> 
> I thought that as such assignments don't change the underlying objects in memory, they would be fine. Compare to:
> 
> immutable(char)[] s1 = "hello world"; //fat pointer to a section of
> immutable memory
> immutable(char)[] s2;  //fat pointer to null
> s2 = s1; //fine
> 
> The only solutions I can think of:
> 1) Use a pointer to a class. Works, but that just seems very unsafe and
> just plain un-D-ish. We also have to dereference two pointers.
> 2) Work entirely with immutable(A)[], which is not quite as crazy as it
> seems as we are copying about arrays of pointers rather than arrays of
> A. It's still quite crazy.
> 3) Write some sort of wrapper struct to hide the pointer.
> 4) Perhaps ref works like C++ int&?
> ref immutable(A) ref_a //error
> Nope.
> 
> So is this intended behavior? Am I missing something obvious?

First off, you're using using a reference, not a pointer. They're similar but quite different. If you were using a pointer, you could do

immutable (A)* a1;

and the object would be immutable while the pointer would be mutable. It's a mutable pointer to an immutable object of type A. The problem with a reference is that it has no syntactic way to differentiate between what's doing the refering and what's being referred. Because immutable is transitive,

immutable A a1;

is an immutable reference to an immutable object of type A. As soon as you try and make a reference immutable, what it refers to is immutable as well. There is no syntactic way to fix the problem. The solution is Rebindable!(T) in std.typecons.

Rebindable!(T) is a wrapper struct. It allows you to have const and immutable references and still change what they refer to. Take this example from the docs:

// Rebindable references to const and immutable objects
void bar()
{
    const w1 = new Widget, w2 = new Widget;
    w1.foo();
    // w1 = w2 would not work; can't rebind const object
    auto r = Rebindable!(const Widget)(w1);
    // invoke method as if r were a Widget object
    r.foo();
    // rebind r to refer to another object
    r = w2;
}

By wrapping the const (or immutable) Widget, you can "rebind" what is "bound" by Rebindable!(T) and effectively get a mutable reference to a const or immutable object. It's not terribly pretty, but apparently no one could come up with a satistfactory way of doing it in the language itself given the syntax for references. So, Rebindable!(T) is the solution.

- Jonathan M Davis
July 16, 2010
On 16/07/10 02:08, Jonathan M Davis wrote:
> On Thursday, July 15, 2010 17:40:26 Gareth Charnock wrote:
>> So having got a collectors' edition TDPL, I though I'd have a try at
>> writing some concurrent code. The idea was a worker thread(s) would do
>> some work and write the results to some immutable objects. These would
>> get passed to an indexer thread that would do neat stuff like indexing
>> the references to the objects in things like hash tables and such,
>> performing any reduce type operations along the way. These views would
>> then be passed back to workers in due course. Unfortunately I hit a
>> snag. It appears you can't do this:
>>
>> class A {
>>      ...
>> }
>> immutable A a1 = new immutable(A);  //pointer to a real, immutable object
>> immutable A a2;  //pointer to null
>> a2 = a1;   //error, guess I doomed a2 to a life of being useless
>>
>> I thought that as such assignments don't change the underlying objects
>> in memory, they would be fine. Compare to:
>>
>> immutable(char)[] s1 = "hello world"; //fat pointer to a section of
>> immutable memory
>> immutable(char)[] s2;  //fat pointer to null
>> s2 = s1; //fine
>>
>> The only solutions I can think of:
>> 1) Use a pointer to a class. Works, but that just seems very unsafe and
>> just plain un-D-ish. We also have to dereference two pointers.
>> 2) Work entirely with immutable(A)[], which is not quite as crazy as it
>> seems as we are copying about arrays of pointers rather than arrays of
>> A. It's still quite crazy.
>> 3) Write some sort of wrapper struct to hide the pointer.
>> 4) Perhaps ref works like C++ int&?
>> ref immutable(A) ref_a //error
>> Nope.
>>
>> So is this intended behavior? Am I missing something obvious?
>
> First off, you're using using a reference, not a pointer. They're similar but
> quite different. If you were using a pointer, you could do
>
> immutable (A)* a1;
>
> and the object would be immutable while the pointer would be mutable. It's a
> mutable pointer to an immutable object of type A. The problem with a reference
> is that it has no syntactic way to differentiate between what's doing the
> refering and what's being referred. Because immutable is transitive,
>
> immutable A a1;
>
> is an immutable reference to an immutable object of type A. As soon as you try
> and make a reference immutable, what it refers to is immutable as well. There is
> no syntactic way to fix the problem. The solution is Rebindable!(T) in
> std.typecons.
>
> Rebindable!(T) is a wrapper struct. It allows you to have const and immutable
> references and still change what they refer to. Take this example from the docs:
>
> // Rebindable references to const and immutable objects
> void bar()
> {
>      const w1 = new Widget, w2 = new Widget;
>      w1.foo();
>      // w1 = w2 would not work; can't rebind const object
>      auto r = Rebindable!(const Widget)(w1);
>      // invoke method as if r were a Widget object
>      r.foo();
>      // rebind r to refer to another object
>      r = w2;
> }
>
> By wrapping the const (or immutable) Widget, you can "rebind" what is "bound" by
> Rebindable!(T) and effectively get a mutable reference to a const or immutable
> object. It's not terribly pretty, but apparently no one could come up with a
> satistfactory way of doing it in the language itself given the syntax for
> references. So, Rebindable!(T) is the solution.
>
> - Jonathan M Davis

Thanks, that should work just fine. Looking at the phobos source for Rebindable, I was nowhere near close to reinventing that wheel.

July 16, 2010
Jonathan M Davis:
> It's not terribly pretty, but apparently no one could come up with a satistfactory way of doing it in the language itself given the syntax for references. So, Rebindable!(T) is the solution.

A helper function can help:

import std.stdio, std.typecons, std.traits;

template isImmutable(T) {
    // I don't know if this works well in all cases
    const bool isImmutable = is(const(T) == T) || is(immutable(T) == T);
}

Rebindable!T rebindable(T)(T obj)
  if ((is(T == class) || is(T == interface) || isArray!T) && isImmutable!T) {
    return Rebindable!T(obj);
}

class Foo { int x, y; }

void main() {
    auto a = rebindable(new immutable(Foo(1, 2)));
    assert(a.sizeof == 4);
    a = new immutable(Foo(3, 4));
}


A template like isImmutable can be useful in Phobos...

Bye,
bearophile