On Friday, 30 August 2024 at 16:32:21 UTC, Jonathan M Davis wrote:
> On Friday, August 30, 2024 9:17:06 AM MDT Steven Schveighoffer via Digitalmars- d wrote:
> On Thursday, 29 August 2024 at 16:44:46 UTC, Nick Treleaven wrote:
> Should we make S() for any struct an error in the next edition?
I have a bold suggestion instead -- let's just start having default constructors.
What's stopping us? We are on the cusp of ridding ourselves of magic runtime hooks, they are all now becoming templates.
For instance, setting the length of an array now calls a template and that template could just call the default constructor if it exists.
Then this whole mess of what S() means vs S.init, or whatnot
becomes much more sane.
It's something we should start thinking about and discussing.
I don't know. Initialization is just simpler if default constructors aren't a thing. Occasionally, it would be really nice to have them, but the vast majority of the time, they're simply not needed. And there's a lot of D code written with the assumption that they're not a thing. It's already problematic that we added the ability to disable default initialization, since most code tends to assume that that isn't really a thing, and it creates annoying corner cases. Similarly, the fact that types can declare an init member just causes problems, because most code assumes that that's not a thing (and I'd argue that it shouldn't be a thing).
The right course of action is:
- Deprecate
init
. Replace it with default(T)
(read: “default of T
”) which gives you an uninitialized object of type T
that might or might not be usable. It can’t be hooked (unlike init
) because default
is a keyword. A constructor transforms a the default(T)
object into an object that satisfies the invariants of T
if any. In general, using default(T)
is a bad idea unless you do it knowing full well what you’re getting.
- Allow default constructors. Those are called on
T()
or variables declared of type T
that are not explicitly initialized. T x;
calls the default constructor of T
.
- A default constructor is only implicitly present if all data members have a default constructor and no constructor is explicitly defined.
- A default constructor can be explicitly set to be the generated one using
default
: default this();
Initialization of a variable:
T x = void; // random garbage; do not use
T y = default; // y contains T’s default; do not use, unless you know it’s okay
T z; // default constructor runs on z; safe to use
x.__default(); // bits default(T) onto x
x.__ctor(args); // runs a constructor on x, which in general expects that x is default(T)
Essentially, T y = default;
is equivalent to T y = void;
plus y.__default()
; both are @system
.
And T z;
is equivalent to T z = default;
plus z.__ctor()
and the language knows that this isn’t inherently unsafe, i.e. @safe
depends on the constructor.
C++ has had default constructors forever. It’s one of the few decisions where I’m convinced that C++ got it right and D isn’t. “Variable declarations must be cheap” is a dogma that I’d expect in a language like C or Zig, not in a language like D, where frequently, safety wins over performance. The fact that I can declare a variable in D and it might not be safe to use is a problem.
In C++, if you design a data structure or algorithm, the question whether a type parameter T
must be default-constructible has an obvious answer (to me at least).
Last but not least (I cut it out, but you did mention it), D has static opCall
, which IMO is a worse design than having default constructors because it means that T()
can mean one thing or another, but worst, it may not return a T
at all! Just thinking about it, init
need not have the right type as well. (And some other magical properties like tupleof
, sizeof
, and other of
s can be defined to lie, too.)