Thread overview
Field Initialiser Reused Across Object Instances
Jan 01
Adam
Jan 01
Adam
January 01
Consider the following:

class A
{
    int x;
}

class B
{
    A a = new A; // I would expect this to be called for each "new B".
}

void main()
{
    import std.stdio;

    auto c = new B;
    writeln("c ", c.a.x);
    c.a.x++;
    writeln("c ", c.a.x);
    writeln;

    auto d = new B;
    writeln("d ", d.a.x, " expected 0!");
    d.a.x++;
    writeln("d ", d.a.x, " expected 1!");
}

*** Output ***
c 0
c 1

d 1 expected 0!
d 2 expected 1!

There is only one instance of A in the above program, although I would expect one for each instance of B.  The field is not marked static.

Is this intended?  My gut reaction is the compiler is memoising "new A" because purity is inferred, but doesn't that contradict the meaning of "new Class" which should always yield an object with it's own identity?

I can fix using a dedicated constructor, but I much prefer initializers where possible.

January 01
On Friday, 1 January 2021 at 13:34:16 UTC, Adam wrote:
>     A a = new A; // I would expect this to be called for each "new B".

Your expectation is wrong for D. Since that's in a `static` context, it is run at compile time.

Any variable marked `static`, `__gshared`, or is struct/class/module-top-level member is a static context. So all those are set up at compile time (or triggers a compile error if this fails btw, either a thing is a static context and thus CTFEd or not a static context and thus never CTFEd) and just blindly copied at run time.

Basically any assignment outside a function is a compile time thing.

> Is this intended?

Yes, it is a frequent thing to surprise people though. The reference there is part of the object but it points to the same default thing because of how that was made.

Constructors are actually run at runtime, but initializers are considered compile time constants.

>  My gut reaction is the compiler is memoising "new A" because purity is inferred

note that ctfe never happens because of anything inferred or pure or stuff like that. It is purely decided by the initialization context.

> I can fix using a dedicated constructor, but I much prefer initializers where possible.

no real choice here, if you want a unique object to be referenced it needs to run with the constructor.
January 01
That's a fantastic answer!  Thank you.  I was not aware that initializers were always compile time, that was the missing piece in my understanding.

It's a shame that I can't use the nicer (IMO) syntax, but the reasoning is sound.