September 20, 2019
On Thursday, 19 September 2019 at 20:25:53 UTC, Jonathan M Davis wrote:
It has to do
> with how D was designed with the idea that every type has an init value which is known at compile time, and various operations rely on being able to simply blit that init value.

We understand the rationale behind the design. The point is that the idea has never worked. If you define a destructor, you most certainly want the object to be non-trivially initialized at run time. New instances of
that type, even "branded" with .init, are NOT initialized - they are in an invalid state. That is why almost every D struct with a destructor is compelled to implement those opCall/external constructor/lazy initialization hacks.

September 20, 2019
On Thursday, 19 September 2019 at 20:25:53 UTC, Jonathan M Davis wrote:
> with(Fixture.cons())
> {
> }
>

That is UB. The spec does not state clearly, in which cases RVO will be applied. One may not assume that the destructor won't be called again on the already destructed object.


September 19, 2019
n Thursday, September 19, 2019 10:42:51 PM MDT Max Samukha via Digitalmars-d wrote:
> On Thursday, 19 September 2019 at 20:25:53 UTC, Jonathan M Davis
> wrote:
> It has to do
>
> > with how D was designed with the idea that every type has an init value which is known at compile time, and various operations rely on being able to simply blit that init value.
>
> We understand the rationale behind the design. The point is that
> the idea has never worked. If you define a destructor, you most
> certainly want the object to be non-trivially initialized at run
> time. New instances of
> that type, even "branded" with .init, are NOT initialized - they
> are in an invalid state. That is why almost every D struct with a
> destructor is compelled to implement those opCall/external
> constructor/lazy initialization hacks.

Sure, the situation is not perfect, but on the whole, IMHO, it works quite well. What it comes down to is that having init the way we do solves a set of problems that C/C++ has but introduces other problems. There is obviously some disagreement on whether the tradeoff is worth it. Personally, I don't think that the problems introduced merit trying to make it so that structs don't have their default value be init.

Regardless, even if we all agreed that we needed to add default constructors for structs, that requires a DIP that goes into great detail about how that's going to work with everything that currently requires that init be the default value for a struct and that it be known at compile time. Given how init works in D, it is not a simple matter to add default constructors, and complaining about it in the newsgroup isn't going to change anything. An actual plan of action would be required, and Walter would have to be convinced that the result is better than what we currently have. Anyone here is free to put in that time and effort if they feel that strongly about it.

- Jonathan M Davis



September 20, 2019
On Friday, 20 September 2019 at 04:42:51 UTC, Max Samukha wrote:
> On Thursday, 19 September 2019 at 20:25:53 UTC, Jonathan M Davis wrote:
> It has to do
>> with how D was designed with the idea that every type has an init value which is known at compile time, and various operations rely on being able to simply blit that init value.
>
> We understand the rationale behind the design. The point is that the idea has never worked. If you define a destructor, you most certainly want the object to be non-trivially initialized at run time. New instances of
> that type, even "branded" with .init, are NOT initialized - they are in an invalid state. That is why almost every D struct with a destructor is compelled to implement those opCall/external constructor/lazy initialization hacks.

This is correct. Around more complex types, the blittability of "init" has always been a polite fiction at best, a schizoid inconsistency at worst. Consider the existence of @disable this, consider that structs can have invariants, which would be completely useless if it had to apply to init - non-zero, non-null, non-empty, practically every invariant used in practice is violated in the init state. We put all this functionality on struct, when the simplest of it has already broken the POD myth.

This is not a new thing. The language has been like this for years. This is not a fight - if there ever was a fight, POD has lost. Phobos is littered with the workarounds of bugs that stem from this confusion. I think it's time to give up and say "yeah, struct .init exists, don't call any methods on it, be extremely careful to dispose of it, preferably just store it in a union field so the broken destructor doesn't get run by accident, it sucks but what can you do." Doing it properly would require explicit lifetime control, but we're not gonna get that any time soon anyway.

I'm not saying `this()` in a struct isn't broken. I'm saying it's not any worse broken than all these other broken things involving structs that we've already learnt to live with over the years, and in the meantime its absence is making code awkward for no realistic benefit.
September 20, 2019
On Thursday, September 19, 2019 11:00:13 PM MDT Max Samukha via Digitalmars- d wrote:
> On Thursday, 19 September 2019 at 20:25:53 UTC, Jonathan M Davis
>
> wrote:
> > with(Fixture.cons())
> > {
> > }
>
> That is UB. The spec does not state clearly, in which cases RVO will be applied. One may not assume that the destructor won't be called again on the already destructed object.

It's exactly the same as

with(Fixture(42))
{
}

would be or

with(Fixture())
{
}

would be if structs had default constructors. My point was that you can get the same behavior as

with(Fixture())
{
}

right now by using a factory function. The syntax just ends up being a bit more verbose. It was the OP who wanted to actually use with in this manner, and if

with(Fixture.cons())
{
}

didn't work, then they could just do something like

auto f = Fixture.cons();
with(f)
{
}

The issue of whether with allows you to construct a temporary that's valid for the entire scope of the with block is a separate issue from whether D should be altered to have default constructors. The OP just happened to use it in their example, and it does currently seem to work even if the spec is silent on the matter. Whether it ever gets changed to not work or the spec gets updated to define its behavior is irrelevant to the issue of default constructors.

- Jonathan M Davis



September 20, 2019
On Friday, 20 September 2019 at 06:01:00 UTC, Jonathan M Davis wrote:
> My point was that you can get the same behavior as
>
> with(Fixture())
> {
> }
>
> right now by using a factory function. The syntax just ends up being a bit more verbose. It was the OP who wanted to actually use with in this manner, and if
>
> with(Fixture.cons())
> {
> }
>
> didn't work, then they could just do something like
>
> auto f = Fixture.cons();
> with(f)
> {
> }
>

For the record, the solution we've settled on as the "best compromise" is an external factory function feeding into a (generated) constructor. That *seems* to work, since the mocker at least is init/copy-safe, and usually none of the fields need to *reference* the other fields. But it's still a workaround that should not be necessary.
September 20, 2019
On Friday, 20 September 2019 at 06:01:00 UTC, Jonathan M Davis wrote:

>
> The issue of whether with allows you to construct a temporary that's valid for the entire scope of the with block is a separate issue from whether D should be altered to have default constructors. The OP just happened to use it in their example, and it does currently seem to work even if the spec is silent on the matter. Whether it ever gets changed to not work or the spec gets updated to define its behavior is irrelevant to the issue of default constructors.
>
> - Jonathan M Davis

Ok, I agree that the unspecified copy elision semantics is a separate issue.


September 20, 2019
On Thursday, 19 September 2019 at 09:02:39 UTC, FeepingCreature wrote:
> We're doing unittesting with fixtures. A Fixture is a struct that contains default values and mocks of the class structure that is being tested. Unittests will have the form
>
> ```
> unittest {
>   with (Fixture()) {
>   }
> }
> ```

I think you should treat a struct like you would treat a D array. would you do:

```
> unittest {
>   with (new int[0]) {
>   }
> }
```
?

I don't think so. You would put something else than the array default value to the with statement.

Same goes for invariants. D code is not in an invalid state if `arr.length == 0 && arr.ptr == null` so an invariant would not check against that. Example of what is really invalid is `arr.length > 0 && arr.ptr != null` - that's the sort of thing invariants should check against, an instance that's not "honestly" null but is not fully constructed, or has impossible values.

And for destructors. in this case, it would be something like:

```
for(auto elemPtr = ptr; elemPtr < ptr + length; elemPtr++) elemPtr.destroy;
```

If the array is in the default value, it's empty and this destructor will do nothing. Same for struct destructors, they should work for "null" values, just not do anything they would normally do.

In fact, you need to do the above even if you never use `.init` values, as if you manually destroy on an instance, it will be set on the default value, and an another destructor will be run on it when it's lifetime ends.

I know this all has the downside that you cannot implement more complex "never null" types in a practical way. But in fact, most of D stuff has "null" anyway: floats, chars, arrays, pointers, classes regardless of the definition. Only bools, integrals and enums (if you don't define one) do not, none of which are complex types. The philosophy AFAIK is that a type should always provide a null value, if it can be assigned normally "wrong" values without enlarging it.
September 20, 2019
On Friday, 20 September 2019 at 07:24:11 UTC, Max Samukha wrote:
> On Friday, 20 September 2019 at 06:01:00 UTC, Jonathan M Davis wrote:
>
>>
>> The issue of whether with allows you to construct a temporary that's valid for the entire scope of the with block is a separate issue from whether D should be altered to have default constructors. The OP just happened to use it in their example, and it does currently seem to work even if the spec is silent on the matter. Whether it ever gets changed to not work or the spec gets updated to define its behavior is irrelevant to the issue of default constructors.
>>
>> - Jonathan M Davis
>
> Ok, I agree that the unspecified copy elision semantics is a separate issue.

I wonder why nobody suggest something like this:

class MyClass {}

struct Fixture
{
  int i;
  immutable MyClass c;
}

auto fixture()
{
  Fixture ret =
  {
    i : 10,
    c : new MyClass()
  };

  return ret;
}

void main()
{
  with(fixture())
  {

  }
}

Andrea
September 20, 2019
On Friday, 20 September 2019 at 07:48:25 UTC, Dukc wrote:
> Example of what is really invalid is `arr.length > 0 && arr.ptr != null`

Meant `arr.length > 0 && arr.ptr == null`