Thread overview
Is this really intended??
Oct 11, 2020
claptrap
Oct 11, 2020
Paul Backus
Oct 11, 2020
Adam D. Ruppe
Oct 11, 2020
claptrap
Oct 11, 2020
claptrap
Oct 11, 2020
claptrap
Oct 11, 2020
Paul Backus
Oct 13, 2020
claptrap
October 11, 2020
struct Foo
{
    int i;
    void opAssign(int rhs) { this.i = rhs; }
    void reset() { i = 0; }
}

class Bar
{
    Foo foo;

    this()
    {
        foo.i = 0;    // *1
        foo.reset();  // *2
        foo = 42;
    }
}

With *1 & *2 commented out...

onlineapp.d(19): Error: cannot implicitly convert expression x of type int to Foo
onlineapp.d(19):        this.foo = x is the first assignment of this.foo therefore it represents its initialization
onlineapp.d(19):        opAssign methods are not used for initialization, but for subsequent assignments

With *2 commented out it compiles with no errors

With *1 commented out...

onlineapp.d(19): Error: cannot implicitly convert expression x of type int to Foo
onlineapp.d(19):        this.foo = x is the first assignment of this.foo therefore it represents its initialization
onlineapp.d(19):        opAssign methods are not used for initialization, but for subsequent assignments

Arn't structs supposed to be default initialised?

But in a constructor a struct member is not? If you write to a field in a struct member suddenly it is considered initialised?

And yet it's OK to call a method on the struct that is not initialised, but if you do it's still uninitialised?

Why should

foo.i = 0;
foo.reset();

Result in the compiler changing whether it's OK to call opAssign?

That's some weird ****.





October 11, 2020
On Sunday, 11 October 2020 at 00:16:53 UTC, claptrap wrote:
> Arn't structs supposed to be default initialised?
>
> But in a constructor a struct member is not? If you write to a field in a struct member suddenly it is considered initialised?
>
> And yet it's OK to call a method on the struct that is not initialised, but if you do it's still uninitialised?
>
> Why should
>
> foo.i = 0;
> foo.reset();
>
> Result in the compiler changing whether it's OK to call opAssign?

The first assignment to a member inside a constructor is considered initialization so that you can use constructors to initialize immutable members:

struct Example
{
    immutable int i;
    this(int i)
    {
        this.i = i; // ok, initialization
        this.i = 42; // error, assignment to immutable variable
    }
}

One consequence of this is that the first assignment does not call opAssign, since opAssign isn't used for initialization.

I agree that this is kind of a hack. A more principled way to handle this would be to introduce a separate syntax for initialization, like `let this.i = i` or `this.i := i`.
October 11, 2020
On Sunday, 11 October 2020 at 00:34:57 UTC, Paul Backus wrote:
> The first assignment to a member inside a constructor is considered initialization so that you can use constructors to initialize immutable members:

This isn't what's weird here though, the bizarre thing is it lets you call the method on the "uninitialized" member, then proceed to initialize it afterward.

The rest of it is justified, but that particular aspect is bizarre and I can't justify that...
October 11, 2020
On Sunday, 11 October 2020 at 00:39:54 UTC, Adam D. Ruppe wrote:
> On Sunday, 11 October 2020 at 00:34:57 UTC, Paul Backus wrote:
>> The first assignment to a member inside a constructor is considered initialization so that you can use constructors to initialize immutable members:
>
> This isn't what's weird here though, the bizarre thing is it lets you call the method on the "uninitialized" member, then proceed to initialize it afterward.
>
> The rest of it is justified, but that particular aspect is bizarre and I can't justify that...

I though structs were default initialised? I mean I remember reading here that one of the design axioms of structs is that that have a simple bitblit for initialisation? Is that not done for members if their enclosing aggregate has a constructor? If you have...

struct Foo { int i; }
struct Bar
{
    Foo foo;
    this() {}
}

Is foo not initialised before entry to Bars constructor? It cant be after, so it must be before, or is it not called at all if foo is explicitly initialised in Foo.this?


October 11, 2020
On Sunday, 11 October 2020 at 09:55:31 UTC, claptrap wrote:
>
> if foo is explicitly initialised in Foo.this?

I mean "Bar.this"
October 11, 2020
On 10/10/20 8:16 PM, claptrap wrote:
> struct Foo
> {
>      int i;
>      void opAssign(int rhs) { this.i = rhs; }
>      void reset() { i = 0; }
> }
> 
> class Bar
> {
>      Foo foo;
> 
>      this()
>      {
>          foo.i = 0;    // *1
>          foo.reset();  // *2
>          foo = 42;
>      }
> }
> 
> With *1 & *2 commented out...
> 
> onlineapp.d(19): Error: cannot implicitly convert expression x of type int to Foo
> onlineapp.d(19):        this.foo = x is the first assignment of this.foo therefore it represents its initialization
> onlineapp.d(19):        opAssign methods are not used for initialization, but for subsequent assignments

Hm.. yeah, this seems not right. The compiler should just go ahead and initialize it if you try to call any methods on it (including opAssign) before initialization.

note that if you define a constructor for Foo that takes an int, it would work (and use that when you assigned it to 42).

But it shouldn't need this, IMO.

What's really bizarre is if you comment *1 out, it still complains. If you called reset on it, is it not already initialized???!

> 
> With *2 commented out it compiles with no errors
> 
> With *1 commented out...
> 
> onlineapp.d(19): Error: cannot implicitly convert expression x of type int to Foo
> onlineapp.d(19):        this.foo = x is the first assignment of this.foo therefore it represents its initialization
> onlineapp.d(19):        opAssign methods are not used for initialization, but for subsequent assignments
> 
> Arn't structs supposed to be default initialised?
> 
> But in a constructor a struct member is not? If you write to a field in a struct member suddenly it is considered initialised?
> 
> And yet it's OK to call a method on the struct that is not initialised, but if you do it's still uninitialised?
> 
> Why should
> 
> foo.i = 0;
> foo.reset();
> 
> Result in the compiler changing whether it's OK to call opAssign?
> 
> That's some weird ****.

This makes no sense. I think this is worth a bug report. The compiler should be a bit smarter (dumber?) on flow checking here.

-Steve
October 11, 2020
On Sunday, 11 October 2020 at 18:57:55 UTC, Steven Schveighoffer wrote:
> On 10/10/20 8:16 PM, claptrap wrote:
>> struct Foo
>> {
>>      int i;
>>      void opAssign(int rhs) { this.i = rhs; }
>>      void reset() { i = 0; }
>> }
>> 
>> class Bar
>> {
>>      Foo foo;
>> 
>>      this()
>>      {
>>          foo.i = 0;    // *1
>>          foo.reset();  // *2
>>          foo = 42;
>>      }
>> }
>> 
>> With *1 & *2 commented out...
>> 
>> onlineapp.d(19): Error: cannot implicitly convert expression x of type int to Foo
>> onlineapp.d(19):        this.foo = x is the first assignment of this.foo therefore it represents its initialization
>> onlineapp.d(19):        opAssign methods are not used for initialization, but for subsequent assignments
>
> Hm.. yeah, this seems not right. The compiler should just go ahead and initialize it if you try to call any methods on it (including opAssign) before initialization.

I think the struct should be initialised full stop. I mean whether a member is initialised before the constructor is run shouldn't be dependent on what you do in the constructor. I mean you declare a struct, whether local or as part of an aggregate, it should be default initialised. Its a simple orthogonal rule.

If it's to do with being able to modify immutable members in the constructor that should be a separate rule. Either "You can modify immutable members in a constructor", or "you can construct immutable members in a constructor, but only once and it must be the first action on the member"

If there's a question of wasted effort initialising a member twice, the compiler should be able to elide the default initialiser, at least in simple cases the mechanism is already there.

They should be separate orthogonal rules.


October 11, 2020
On Sunday, 11 October 2020 at 21:50:28 UTC, claptrap wrote:
>
> I think the struct should be initialised full stop. I mean whether a member is initialised before the constructor is run shouldn't be dependent on what you do in the constructor. I mean you declare a struct, whether local or as part of an aggregate, it should be default initialised. Its a simple orthogonal rule.

This is how the language already works, at least according to the spec:

    When an instance of a struct is created, the following steps happen:

        1. The raw data is statically initialized using the values provided in
        the struct definition. This operation is equivalent to doing a memory
        copy of a static version of the object onto the newly allocated one.

        2. If there is a constructor defined for the struct, the constructor
        matching the argument list is called.

        3. If struct invariant checking is turned on, the struct invariant is
        called at the end of the constructor.


Source: https://dlang.org/spec/struct.html#struct-instantiation
October 11, 2020
On 10/11/20 5:50 PM, claptrap wrote:
> On Sunday, 11 October 2020 at 18:57:55 UTC, Steven Schveighoffer wrote:
>> On 10/10/20 8:16 PM, claptrap wrote:
>>> struct Foo
>>> {
>>>      int i;
>>>      void opAssign(int rhs) { this.i = rhs; }
>>>      void reset() { i = 0; }
>>> }
>>>
>>> class Bar
>>> {
>>>      Foo foo;
>>>
>>>      this()
>>>      {
>>>          foo.i = 0;    // *1
>>>          foo.reset();  // *2
>>>          foo = 42;
>>>      }
>>> }
>>>
>>> With *1 & *2 commented out...
>>>
>>> onlineapp.d(19): Error: cannot implicitly convert expression x of type int to Foo
>>> onlineapp.d(19):        this.foo = x is the first assignment of this.foo therefore it represents its initialization
>>> onlineapp.d(19):        opAssign methods are not used for initialization, but for subsequent assignments
>>
>> Hm.. yeah, this seems not right. The compiler should just go ahead and initialize it if you try to call any methods on it (including opAssign) before initialization.
> 
> I think the struct should be initialised full stop. I mean whether a member is initialised before the constructor is run shouldn't be dependent on what you do in the constructor. I mean you declare a struct, whether local or as part of an aggregate, it should be default initialised. Its a simple orthogonal rule.

I didn't say it right - I mean that if you call a method on a struct, it should be treated as "initialized". It already initializes all the memory anyway.

> If it's to do with being able to modify immutable members in the constructor that should be a separate rule. Either "You can modify immutable members in a constructor", or "you can construct immutable members in a constructor, but only once and it must be the first action on the member"

It has to do with this:

struct S
{
   this(int) { writeln("ctor"); }
   void opAssign(int) { writeln("assignment"); }
}

S s = 5; // ctor
s = 5; // assignment

It is treating the first assignment of a member in a struct ctor as the initializer for the member. But if you have no ctor that matches, I see no reason why it shouldn't treat this as initializing (via default init), AND THEN calling opAssign on that.

It also makes NO sense to treat any usage of the struct after calling a member function on it as initializing. That seems to me a bug in the compiler implementation. That first function could have done anything to the struct.

> If there's a question of wasted effort initialising a member twice, the compiler should be able to elide the default initialiser, at least in simple cases the mechanism is already there.
> 
> They should be separate orthogonal rules.

The default initializer is happening anyway -- the opAssign or constructor is expecting it.

-Steve
October 13, 2020
On Sunday, 11 October 2020 at 22:11:48 UTC, Steven Schveighoffer wrote:
> On 10/11/20 5:50 PM, claptrap wrote:
>> On Sunday, 11 October 2020 at 18:57:55 UTC, Steven Schveighoffer wrote:
>>> On 10/10/20 8:16 PM, claptrap wrote:
>>
>> If there's a question of wasted effort initialising a member twice, the compiler should be able to elide the default initialiser, at least in simple cases the mechanism is already there.
>> 
>> They should be separate orthogonal rules.
>
> The default initializer is happening anyway -- the opAssign or constructor is expecting it.

I didn't realise the default initialiser and constructor were separate required events. I thought default initialisation was enough, the constructors is there if you want something more than the default init.

I guess that the error message isn't very clear either since it talks about initialisation when really it's construction that is needed. I mean if the language is going to differentiate between initialisation and construction, the error message should be clear which one is missing.

In fact if the struct has no constructors it can only be default initialised anyway, so it's seems pointless for the compiler to require a constructor to be called when it's essentially a NOP.

I mean if the struct has no constructor, then it should be considered fully constructed after the default init.