Comment #0 by qs.il.paperinik — 2024-03-26T17:03:26Z
When a class or struct has a member of reference type (common cases: a class, a slice, an AA, a pointer, but also a struct type with indirections), require that the initializer be an immutable lvalue.
Otherwise, it should be an error.
Example code:
```d
class C { } // not important what’s in there
struct S
{
static C static_c = new C();
static const C static_const_c = static_c;
static immutable immut_c = new immutable C();
C c1 = new C(); // error: rvalue
immutable C c2 = new immutable C(); // error: rvalue
const C c3 = static_c; // error: (possibly) mutable
immutable C c4 = immut_c; // ok: lvalue && immutable
}
```
Rationale:
Bad case `c1` is bad because default-initialized `S` instances share the same `c1` value. This is defined behavior, but highly unexpected and counter-intuitive for programmers coming from other languages such as Java and C#, where the `new` expression is essentially placed to the constructor.
This instance is mutable, meaning when declaring a `S` variable and mutating the `c1` member, this change affects all (future) `S` values.
Bad case `c2`: Has the same issue as `c1` except the mutation part. Still, two default-constructed `S` values have the very same `c1` object, which is counter-intuitive.
If `C` objects are constructed by a `pure` constructor, this case is largely not that bad. However, if `C` objects are individually constructed, such that even with equal parameters, there are meaningful differences between instances, this can lead to a lot of confusion.
Bad case `c3`: Here it is clear that the instance is the same one, given the programmer understands reference types: The same static instance is used. However, this instance is mutable, so all `S` instances initially share the same state mutable. Making this an error avoids a foot-gun. As the example shows, the fact that the initializer is `const` does not help. A `const` object may (and in this example clearly does) have a mutable object underlying it.
Good case `c4`: While as with `c3`, one needs to understand reference type semantics, the problem is minor. While all `S` instances share the same `c4` object, which is also quite clearly the case, this object is additionally immutable. Even in cases where a programmer mistakes this initialization for a deep copy and incorrectly assumes `S` objects don’t share `c4`s, this conflation is largely unproblematic.
---
The corrective action suggested by the compiler would be to move the initialization to constructors. There, any of the above cases can be achieved, but in particular, the case for `c1` changes meaning largely to how programmers (from other languages such as Java and C#) expect. In this case, if the actual meaning was intended, the programmer must be more explicit about it and e.g. define a private static variable.
It may be noteworthy that D disallows otherwise defined syntax for the mere purpose of not confusing programmers with experience in other languages. It is designed like that initially (e.g. operator precedence of bitwise and comparison) and introduced breaking changes, e.g. https://issues.dlang.org/show_bug.cgi?id=16001
Comment #1 by schveiguy — 2024-03-26T17:44:01Z
case c2 seems fine to me. What is the problem with it? This seems akin to string interning.
Comment #2 by nick — 2024-03-27T11:25:57Z
> static C static_c = new C(); // line 5
> static const C static_const_c = static_c;
fieldrefinit.d(5): Error: variable `fieldrefinit.S.static_c` is a thread-local class and cannot have a static initializer. Use `static this()` to initialize instead.
fieldrefinit.d(6): Error: static variable `static_c` cannot be read at compile time
It compiles if static_c is made const/immutable, but then c3 is fine.
The problem with c2 is issue 10376. I agree that c1 should not be allowed.
Comment #3 by nick — 2024-03-27T12:00:37Z
> The problem with c2 is issue 10376
Sorry, that's not what's happending for c2. I also don't understand why c2 is bad.
Comment #4 by qs.il.paperinik — 2024-04-29T15:24:20Z
(In reply to Steven Schveighoffer from comment #1)
> case c2 seems fine to me. What is the problem with it? This seems akin to
> string interning.
Strings barely have identity, but class objects in general have identity. One might expect every `S` default initialized instance has its own `immutable C` object referenced by `c2`, but that’s not the case! Surprising programmers simply isn’t a great idea and a workaround is easy.
Comment #5 by robert.schadek — 2024-12-13T19:34:04Z