[Issue 24454] New: Disallow initialization of non-static reference type data members by non-immutable values or rvalues
d-bugmail at puremagic.com
d-bugmail at puremagic.com
Tue Mar 26 17:03:26 UTC 2024
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 at puremagic.com
Reporter: qs.il.paperinik at 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
--
More information about the Digitalmars-d-bugs
mailing list