Thread overview
immutable postblit unusable?
Feb 11, 2018
Cauterite
Feb 11, 2018
Jonathan M Davis
Feb 11, 2018
Cauterite
Feb 12, 2018
Cauterite
February 11, 2018
https://dpaste.dzfl.pl/80d2a208519c

struct A {
	this(this) immutable {}
}

struct B {
	A a;
}

// Error: immutable method f18.A.__postblit is not callable using a mutable object

-------------------------

I honestly don't know what to make of this.
I guess the auto-generated postblit of B is throwing this?

Does it even make sense to have an immutable postblit?
Should such code be explicitly rejected?
February 11, 2018
On Sunday, February 11, 2018 07:50:52 Cauterite via Digitalmars-d wrote:
> https://dpaste.dzfl.pl/80d2a208519c
>
> struct A {
>   this(this) immutable {}
> }
>
> struct B {
>   A a;
> }
>
> // Error: immutable method f18.A.__postblit is not callable using a mutable object
>
> -------------------------
>
> I honestly don't know what to make of this.
> I guess the auto-generated postblit of B is throwing this?
>
> Does it even make sense to have an immutable postblit? Should such code be explicitly rejected?

Basically, as soon as you have a postblit constructor, the type cannot be const or immutable. Unfortunately, as nice as the idea for postblit constructors is in comparison to copy constructors, it fundamentally doesn't work if the members aren't mutable (since for the postblit constructor to work, you have to mutate the result). Occasionally, there has been talk of adding copy constructors to solve the problem with const, but it's never happened.

In the case of immutable, the postblit is actually completely unnecessary, because there's no need to do a deep copy of an immutable object, since it's guaranteed to never change its value (whereas with const, a deep copy can still be necessary, since other references to that data may be mutable and could thus change the data).

Now, as to what you've done. You've marked the postblit constructor as immutable. That means that it's only callable when the object is immutable, and A isn't immutable, so B's implicit postblit constructor can't work properly. And declaring an explicit postblit constructor doesn't help. The reason for that is that any type with an explicit postblit constructor really has two posblit constructors. This code

struct A
{
}
pragma(msg, __traits(allMembers, A));

struct B
{
    this(this) {}
}
pragma(msg, __traits(allMembers, B));

struct C
{
    B c;
}
pragma(msg, __traits(allMembers, C));

prints this

tuple()
tuple("__postblit", "__xpostblit", "opAssign")
tuple("b", "__xpostblit", "opAssign")

A doesn't have an explicit postblit constructor, and it doesn't have any member variables with a postblit constructor. So, it has no postblit constructors. On the other hand, B has an explicit postblit constructor. That's the __postblit in the members, and __xpostblit is the implicit postblit constructor. C then has no explicit postblit constructor, so it has no __postblit member, but because it has a member variable with a postblit constructor, it has a __xpostblit member.

As I understand it, __xpostblit calls the postblit constructors for each member that needs it and then calls __postblit. So, __xpostblit is essentially the actual postblit constructor that deals correctly with the entire type and all its members recursively, and __postblit is the explicitly declared one that just deals with the type itself.

And with what you've done, you've declared __postblit to be immutable, but that has no effect on __xpostblit. So, you get a compilation error about mutable objects not working with __postblit, since __xpostblit only operates on mutable objects.

Interestingly, changing your example so that A a; is immutable doesn't actually help, and it could be argued that that's a bug, but it really doesn't matter. If you're dealing with immutable, there's zero reason to have a postblit constructor such that it arguably should be an error to declare a postblit constructor as immutable, and the nature of a postblit constructor fundamentally only works with mutable objects, so arguably, it should be illegal to mark a postblit constructor with either const or immutable.

But if you just give up on the postblit constructor, immutable will work just fine. It's const that's screwed, and unless there's a language improvement (most likely involving some kind of copy constructor), const and objects with postblit constructors just fundamentally aren't going to work together.

- Jonathan M Davis

February 11, 2018
On Sunday, 11 February 2018 at 08:25:41 UTC, Jonathan M Davis wrote:
> 
Thanks, I couldn't have asked for a more thorough explanation.
Especially that __xpostblit detail, which I now vaguely recall seeing in the runtime code.

Sounds like making `this(this) immutable` illegal is the way to go.

I don't have anything to add about the `const` problem though.
February 12, 2018
issue opened here:
https://issues.dlang.org/show_bug.cgi?id=18417

thanks Jon
February 13, 2018
On 2/11/18 3:25 AM, Jonathan M Davis wrote:
> In the case of immutable, the postblit is actually completely unnecessary,
> because there's no need to do a deep copy of an immutable object, since it's
> guaranteed to never change its value (whereas with const, a deep copy can
> still be necessary, since other references to that data may be mutable and
> could thus change the data).

What about reference counting? Essentially, you could be changing values external to the type itself.

I don't think postblit should be illegal on immutable types.

-Steve