digitalmars.D - Disallowing S() when struct S has a constructor
- Nick Treleaven (31/31) Aug 29 ```d
- Nick Treleaven (2/3) Aug 29 I meant any struct *with a constructor*.
- Kapendev (4/8) Aug 30 I think the current behavior is fine. As people said, you can
- Jonathan M Davis (8/9) Aug 29 I know that some folks use S() instead of S.init, because S.init still w...
- Steven Schveighoffer (12/13) Aug 30 I have a bold suggestion instead -- let's just start having
- Jonathan M Davis (64/76) Aug 30 I don't know. Initialization is just simpler if default constructors are...
- Quirin Schroll (49/81) Sep 05 The right course of action is:
- Paul Backus (11/24) Sep 05 Wouldn't it be easier to just forbid user-defined `.init`
- Quirin Schroll (11/35) Sep 06 In principle, yes. If every data member’s type `T` supports
- Paul Backus (9/19) Sep 06 My point is: so far, we have been fine with requiring the
- Matheus Catarino (5/12) Sep 07 Apparently Zig is also about to adopt `.init`/`.default`/`.empty`
- Quirin Schroll (22/36) Sep 13 As I read the thread, it’s specific to `GeneralPurposeAllocator`
- Ogi (4/11) Aug 30 I have a much less radical
- Paul Backus (26/39) Aug 30 There is currently one situation in the D language where `S()`
- Kapendev (4/18) Aug 30 I also don't like this for the same reason. Will break things
- Imperatorn (3/17) Sep 18 +1 for default constructor
```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
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
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: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.Should we make S() for any struct an error in the next edition?I meant any struct *with a constructor*.
Aug 30
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
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
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: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 DavisShould 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.
Aug 30
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: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.)On Thursday, 29 August 2024 at 16:44:46 UTC, Nick Treleaven wrote: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).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.
Sep 05
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
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: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`.- 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?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.- 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 06
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: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.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.
Sep 06
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
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: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.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 13
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
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: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.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
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:I also don't like this for the same reason. Will break things that currently work fine.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
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:+1 for default constructorShould 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
Sep 18