March 26 [Issue 24454] New: Disallow initialization of non-static reference type data members by non-immutable values or rvalues | ||||
---|---|---|---|---|
| ||||
https://issues.dlang.org/show_bug.cgi?id=24454 Issue ID: 24454 Summary: Disallow initialization of non-static reference type data members by non-immutable values or rvalues Product: D Version: D2 Hardware: All OS: All Status: NEW Severity: enhancement Priority: P1 Component: dmd Assignee: nobody@puremagic.com Reporter: qs.il.paperinik@gmail.com 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 -- |
Copyright © 1999-2021 by the D Language Foundation