This discussion is extracted out from the (sometimes intense) discussion that occurred on the following issue: #5552
The crux of the problem is that there is a bit of an ergonomic cliff you fall off of when you transition from writing structs like so:
struct Vector3d { double X, Y, Z; } var v = new Vector3d();
versus something like:
struct MagnitudeVector3d { double X, Y, Z; double Magnitude = 1; } var m = new MagnitudeVector3d();
Due to the change the LDM decided on https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-01-03.md#parameterless-struct-constructors-revisited, the latter is no longer legal, and the user must provide a real constructor to initialize the struct. We want the user to provide a constructor explicitly as we do not want to synthesize an implicit no-arg constructor here that would then disappear if they ever added a has-arg constructor.
That said, for the user, adding this constructor is not pleasant. They end up having to write things like:
struct MagnitudeVector3d { double X, Y, Z; double Magnitude = 1; public MagnitudeVector3d() { X = 0; Y = 0; Z = 0; } } // or something like: struct MagnitudeVector3d { double X = 0, Y = 0, Z = 0; double Magnitude = 1; public MagnitudeVector3d() { } }
Both of these approaches are necessary so that the struct is definitely-assigned (DA) on constructor exit.
Note that this friction is a particular problem for the no-arg constructor. For any with-arg constructor, we always supported the user writing things like the following:
struct Vector3d { double X, Y, Z; public Vector3d(double X) : this() { /*... */ } }
In other words, you could always chain the with-arg constructor to teh no-arg constructor. A constructor always leaves the struct def assigned, so this always worked as the chained constructor init'ed everything to zero, and then the actual constructor logic could run after that.
However, there's no equivalent for the no-arg constructor if the user provides it. the user cannot write:
struct MagnitudeVector3d { double X = 0, Y = 0, Z = 0; double Magnitude = 1; public MagnitudeVector3d() : this() { } }
As this would be a circular reference. They also can't do:
struct MagnitudeVector3d { double X = 0, Y = 0, Z = 0; double Magnitude = 1; public MagnitudeVector3d() { this = default; } }
As this would blow away the initialized for Magnitude
, defeating the purpose of having initializers in the first place.
--
There are things we could do here though to make things more palatable for the user (with varying levels of pros/cons).
Options:
default
on exit leaving the struct in a DA state.
public MagnitudeVector3d() { }
and get reasonable behavior.public MagnitudeVector3d() { }
and get reasonable behavior.public MagnitudeVector3d() : default {}
public record struct Foo(double X, double Y) : default { }
is totally sufficient for taht.RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4