Jump to page: 1 2
Thread overview
Disallowing S() when struct S has a constructor
Aug 31
Kapendev
Aug 30
Ogi
Aug 31
Kapendev
August 29
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:

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?

August 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.

August 29
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



August 30

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

August 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:
> > 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



August 30

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. A struct with a default constructor can only be initialized by a constructor call, just like a struct with a disabled default constructor.

August 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:

>

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. 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.

August 31

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.

August 31

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.

September 05

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 ofs can be defined to lie, too.)

« First   ‹ Prev
1 2