www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Disallowing S() when struct S has a constructor

reply Nick Treleaven <nick geany.org> writes:
```d
struct S
{
     int i;
     this(int);
}

S a = {}; // error, use ctor instead
S b = S(); // fine!
```
Why isn't the last line also disallowed (like `a`'s struct 
initializer) when there's a constructor? If the answer is that 
struct constructors don't need to be called to create a valid 
struct instance, then why disallow the struct initializer?

It's also inconsistent with classes. If you want a default 
initialized literal you can write S.init. There is one difference 
for nested structs, that S() will do runtime initialization of 
the context pointer hidden field.

Allowing S() means we can't diagnose code trying to call a 
variadic constructor with no arguments - it silently fails:

```d
struct S
{
     this(int[]...) { writeln("ctor"); }
}

void main()
{
     S s;
     s = S(); // huh, no ctor call
}
```

Should we make S() for any struct an error in the next edition?
Aug 29
next sibling parent reply Nick Treleaven <nick geany.org> writes:
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 meant any struct *with a constructor*.
Aug 29
parent Kapendev <alexandroskapretsos gmail.com> writes:
On Thursday, 29 August 2024 at 16:49:03 UTC, Nick Treleaven 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 meant any struct *with a constructor*.
I think the current behavior is fine. As people said, you can disable the default constructor if needed. A change like this would break too much code, making any benefit a bit useless.
Aug 30
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, August 29, 2024 10:44:46 AM MDT Nick Treleaven via Digitalmars-d 
wrote:
 Should we make S() for any struct an error in the next edition?
I know that some folks use S() instead of S.init, because S.init still works when default initialization is disabled, but S() doesn't. So, if you use S() in a context where you would typically use S.init, then you avoid accidentally using the init value of a struct that has disabled default initialization. - Jonathan M Davis
Aug 29
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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. -Steve
Aug 30
next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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). I think that to really decide on this intelligently, we would need a very detailed analysis of what exactly adding default constructors would change and how it would interact with existing features. It's one of those things that seems simple at first glance (especially if you're just thinking about the case of declaring a stack variable), but the way that init works is pretty deeply ingrained into D's DNA. And even if we all agreed that we would ideally have default constructors, with all of the code that already exists which explicitly uses the init value of a type (including a lot of templated code) or which assumes that the default value of a type is known at compile time, adding default constructors into the mix could subtly break a bunch of stuff. I could easily see it being the case that we can cleanly add default constructors to most parts of the language only to find some corner case where it's a serious problem. Similarly, I could easily see it being the case that we're able to cleanly deal with all of the language issues and make it work, but because of all of the existing code that assumes that it's not a thing, we have some pretty serious problems in practice. It could also very well be that we could make it work quite well, but I strongly suspect that at minimum, the general assumption that init is the default value of a type is going to make it so that adding default constructors will be pretty problematic in practice even if it would work fine in theory. So, I don't really want to say that we should do it or that we shouldn't. And I think that it's a very different question if we're looking at whether we would do it if were creating D from scratch vs there already being a bunch of existing D code. I have no problem with us exploring the idea, but I think that this is a case where if we want to seriously consider making a change, we need a _very_ detailed analysis of the situation with it being very clear how the changes would affect things so that we can weigh the pros and cons with all of that information - and I suspect that none of us are going to really want to take the time to do that (and it will likely take someone with a very detailed understanding of a bunch of the intricate details to be able to do the analysis correctly). The simplest solution would likely be to simply make it like structs that have disabled default initialization in that types with default constructors won't work in a bunch of situations (potentially making it so that they only really work when you're just looking to declare one as a stack variable), but if that's what someone wants, at the moment, they could just disable default initialization on the type and give it a static opCall, forcing you to do something like auto foo = Foo(); instead of Foo foo; like someone might want to do, but functionally, it's the same. And since default initialization would be disabled, no one could make the mistake of doing Foo foo; by accident instead of auto foo = Foo(); So, I'm not sure that it's really a good idea to add a language solution just for that case - but it's also something that's much easier to reason about than adding default constructors in general. - Jonathan M Davis
Aug 30
parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
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: ```d 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.)
Sep 05
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Thursday, 5 September 2024 at 17:46:18 UTC, Quirin Schroll 
wrote:
 - 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.
Wouldn't it be easier to just forbid user-defined `.init` properties? It's a breaking change either way.
 - A default constructor is only implicitly present if all data 
 members have a default constructor and no constructor is 
 explicitly defined.
Would this also apply if some members have default constructors and the others are things like integers or POD structs?
 - A default constructor can be explicitly set to be the 
 generated one using `default`: `default this();`
We already have compiler-generated default implementations for copy constructors and `opAssign`, and neither of them support this kind of explicit declaration. Why is it necessary to have this for default constructors, if it wasn't necessary for copy constructors and `opAssign`?
Sep 05
parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Thursday, 5 September 2024 at 18:17:33 UTC, Paul Backus wrote:
 On Thursday, 5 September 2024 at 17:46:18 UTC, Quirin Schroll 
 wrote:
 - 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.
Wouldn't it be easier to just forbid user-defined `.init` properties? It's a breaking change either way.
 - A default constructor is only implicitly present if all data 
 members have a default constructor and no constructor is 
 explicitly defined.
Would this also apply if some members have default constructors and the others are things like integers or POD structs?
In principle, yes. If every data member’s type `T` supports `T()`, I consider it having a default constructor. The reason why `T()` works is irrelevant, maybe excluding `static opCall`.
 - A default constructor can be explicitly set to be the 
 generated one using `default`: `default this();`
We already have compiler-generated default implementations for copy constructors and `opAssign`, and neither of them support this kind of explicit declaration. Why is it necessary to have this for default constructors, if it wasn't necessary for copy constructors and `opAssign`?
Because defining any constructor makes the compiler not generate a default constructor. It’s a way to say: Give me the default constructor without spelling it out. Maybe I’m leaning too much into the C++ direction, and `this() {}` would absolutely work. But generally, `default this();` would make the default constructor have attributes and `this() {}` would not. If that’s the only difference, it maybe isn’t worth it.
Sep 06
parent Paul Backus <snarwin gmail.com> writes:
On Friday, 6 September 2024 at 09:45:39 UTC, Quirin Schroll wrote:
 On Thursday, 5 September 2024 at 18:17:33 UTC, Paul Backus 
 wrote:
 We already have compiler-generated default implementations for 
 copy constructors and `opAssign`, and neither of them support 
 this kind of explicit declaration. Why is it necessary to have 
 this for default constructors, if it wasn't necessary for copy 
 constructors and `opAssign`?
Because defining any constructor makes the compiler not generate a default constructor. It’s a way to say: Give me the default constructor without spelling it out.
My point is: so far, we have been fine with requiring the programmer to spell it out in other cases. Why is that unacceptable in this case specifically? Maybe the answer is, "it's not just a problem in this case, the programmer shouldn't have to spell it out in any of those other cases either." If so, then this should be a more general language feature, with its own DIP separate from any default-constructor proposal.
Sep 06
prev sibling parent reply Matheus Catarino <matheus-catarino hotmail.com> writes:
On Thursday, 5 September 2024 at 17:46:18 UTC, Quirin Schroll 
wrote:
 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.
Apparently Zig is also about to adopt `.init`/`.default`/`.empty` during variable declaration. Even if there is no ctor/dtor (RAII). https://ziggit.dev/t/prefered-method-to-initialize-generalpurposeallocator/5875/13
Sep 07
parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Sunday, 8 September 2024 at 00:00:49 UTC, Matheus Catarino 
wrote:
 On Thursday, 5 September 2024 at 17:46:18 UTC, Quirin Schroll 
 wrote:
 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.
Apparently Zig is also about to adopt `.init`/`.default`/`.empty` during variable declaration. Even if there is no ctor/dtor (RAII). https://ziggit.dev/t/prefered-method-to-initialize-generalpurposeallocator/5875/13
As I read the thread, it’s specific to `GeneralPurposeAllocator` and other structs for which compile-time default initialization can’t be done. Zig’s `init` is a fully formed object that is a run-time-initialized value specifically crafted for each type that has it, very much unlike D’s `init`s. Part of Zig’s dogma is that everything that happens has to be explicit. Technically, there are no destructors in Zig, just functions you’re supposed to call after an object isn’t needed anymore, which you normally do using `defer`. The difference is, every use of an object requires a `defer deinit();`, i.e. there is no implicit destructor call. In D, we’re comfortable with destructors and other implicit stuff, so a declaration implicitly calling a default constructor is very much in line with what D does elsewhere. It’s not even close to being that implicit as a destructor call, actually. “Make interfaces easy to use correctly and hard to use incorrectly.” ―Scott Meyers https://www.aristeia.com/Papers/IEEE_Software_JulAug_2004_revised.htm A struct type for which `S s; s.f();` doesn’t really work, but `S s = S(); s.f();` is bad design.
Sep 13
prev sibling next sibling parent reply Ogi <ogion.art gmail.com> writes:
On Friday, 30 August 2024 at 15:17:06 UTC, Steven Schveighoffer 
wrote:
 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.
I have a much less radical [proposal](https://forum.dlang.org/thread/ekskxgqdyyajbagnxf r forum.dlang.org). A struct with a default constructor can only be initialized by a constructor call, just like a struct with a disabled default constructor.
Aug 30
parent Paul Backus <snarwin gmail.com> writes:
On Friday, 30 August 2024 at 16:58:25 UTC, Ogi wrote:
 On Friday, 30 August 2024 at 15:17:06 UTC, Steven Schveighoffer 
 wrote:
 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.
I have a much less radical [proposal](https://forum.dlang.org/thread/ekskxgqdyyajbagnxf r forum.dlang.org). A struct with a default constructor can only be initialized by a constructor call, just like a struct with a disabled default constructor.
There is currently one situation in the D language where `S()` and `S.init` have different results: when `S` is a nested struct (that is, defined inside a function). In this situation, `S()` produces an instance with its context pointer properly initialized to point to the enclosing stack frame, and `S.init` produces an instance with its context pointer set to `null`. This is, for all intents and purposes, a (compiler-generated) default constructor. Additional code is executed at runtime when you use `S()` instead of `S.init`. Furthermore, if you use default initialization with one of these structs, it behaves like `S()`, not `S.init`: void main() { int n; struct S { void fun() { ++n; } } S s; assert(s is S()); assert(s !is S.init); } For the sake of consistency, if we allow user-defined default constructors for structs, they should behave the same way.
Aug 30
prev sibling next sibling parent Kapendev <alexandroskapretsos gmail.com> writes:
On Friday, 30 August 2024 at 15:17:06 UTC, Steven Schveighoffer 
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. -Steve
I also don't like this for the same reason. Will break things that currently work fine.
Aug 30
prev sibling parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Friday, 30 August 2024 at 15:17:06 UTC, Steven Schveighoffer 
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. -Steve
+1 for default constructor
Sep 18