www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Why does this not compile?

reply Shachar Shemesh <shachar weka.io> writes:
void main() {
     struct S {
         uint value;

         ~this() {
         }
     }

     const S a = S(12);
     S b = a;
}

test.d(10): Error: cannot implicitly convert expression a of type 
const(S) to S

Doing *any* of the following makes the code compile:
* Making the struct "static"
* Making the struct global (essentially same as above)
* Removing the struct's destructor

I can kinda see why it won't compile without making it static. There is 
a hidden pointer to the frame that is const, implying the frame is also 
const. This constness would be overridden if the assignment is allowed 
to go through. I don't think this is a very good reason (see below), but 
I understand it.

What I do not understand is why removing the destructor solves the error.




While I get while the compiler treats the frame pointer as const, I 
should point out that if I add a static variable to the struct, that one 
remains mutable even when the instance itself is const. There is no 
inherent difference between a variable stored as static and a variable 
stored in the context frame.

And before you answer with "in D pointer constness is transitive", allow 
me to point something out: It is not possible to ever change the frame 
pointer of a struct. That pointer is, effectively, always const.

Shachar
Mar 06 2018
next sibling parent reply Diego <diego example.com> writes:
On Tuesday, 6 March 2018 at 10:03:54 UTC, Shachar Shemesh wrote:
 void main() {
     struct S {
         uint value;

         ~this() {
         }
     }

     const S a = S(12);
     S b = a;
 }
You cannot assign a const element (`a`) to a non-const element (`b`) in `S b = a` expression. To make de assignment, you have to cast a to a non-constant expression: S b = cast(S)a; Or make `b` as const: const S b = a; Or, better, use auto keyword: auto b = a;
Mar 06 2018
parent Shachar Shemesh <shachar weka.io> writes:
On 06/03/18 12:16, Diego wrote:
 
 You cannot assign a const element (`a`) to a non-const element (`b`) in 
 `S b = a` expression.
Sure you can. During assignment you are making a copy. Why do you care whether the original is const? Shachar
Mar 06 2018
prev sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 6 March 2018 at 10:03:54 UTC, Shachar Shemesh wrote:
 void main() {
     struct S {
         uint value;

         ~this() {
         }
     }

     const S a = S(12);
     S b = a;
 }

 test.d(10): Error: cannot implicitly convert expression a of 
 type const(S) to S
Looks like a bug to me - please file one in bugzilla. -- Simen
Mar 06 2018
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 3/6/18 6:21 AM, Simen Kjærås wrote:
 On Tuesday, 6 March 2018 at 10:03:54 UTC, Shachar Shemesh wrote:
 void main() {
     struct S {
         uint value;

         ~this() {
         }
     }

     const S a = S(12);
     S b = a;
 }

 test.d(10): Error: cannot implicitly convert expression a of type 
 const(S) to S
Looks like a bug to me - please file one in bugzilla.
Nope. It's not a bug. S contains a pointer, namely the context pointer for main. https://dlang.org/spec/struct.html#nested Try this: void main() { static struct S { uint value; ~this() {} } ... // rest of your code } -Steve
Mar 06 2018
next sibling parent reply Shachar Shemesh <shachar weka.io> writes:
On 06/03/18 14:00, Steven Schveighoffer wrote:
 On 3/6/18 6:21 AM, Simen Kjærås wrote:
 On Tuesday, 6 March 2018 at 10:03:54 UTC, Shachar Shemesh wrote:
 void main() {
     struct S {
         uint value;

         ~this() {
         }
     }

     const S a = S(12);
     S b = a;
 }

 test.d(10): Error: cannot implicitly convert expression a of type 
 const(S) to S
Looks like a bug to me - please file one in bugzilla.
Nope. It's not a bug. S contains a pointer, namely the context pointer for main. https://dlang.org/spec/struct.html#nested
Which does not explain why my code compiles if I remove the destructor. Shachar
Mar 06 2018
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 3/6/18 7:44 AM, Shachar Shemesh wrote:
 On 06/03/18 14:00, Steven Schveighoffer wrote:
 On 3/6/18 6:21 AM, Simen Kjærås wrote:
 On Tuesday, 6 March 2018 at 10:03:54 UTC, Shachar Shemesh wrote:
 void main() {
     struct S {
         uint value;

         ~this() {
         }
     }

     const S a = S(12);
     S b = a;
 }

 test.d(10): Error: cannot implicitly convert expression a of type 
 const(S) to S
Looks like a bug to me - please file one in bugzilla.
Nope. It's not a bug. S contains a pointer, namely the context pointer for main. https://dlang.org/spec/struct.html#nested
Which does not explain why my code compiles if I remove the destructor.
If the struct is POD, there is no need to include the context pointer. void main() { struct POD { int x; } pragma(msg, POD.sizeof); // 4 struct Nested { int x; void someMember() {} } pragma(msg, Nested.sizeof); // 16 } -Steve
Mar 06 2018
prev sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 6 March 2018 at 12:00:43 UTC, Steven Schveighoffer 
wrote:
 On 3/6/18 6:21 AM, Simen Kjærås wrote:
 On Tuesday, 6 March 2018 at 10:03:54 UTC, Shachar Shemesh 
 wrote:
 void main() {
     struct S {
         uint value;

         ~this() {
         }
     }

     const S a = S(12);
     S b = a;
 }

 test.d(10): Error: cannot implicitly convert expression a of 
 type const(S) to S
Looks like a bug to me - please file one in bugzilla.
Nope. It's not a bug. S contains a pointer, namely the context pointer for main.
It's a bug. As pointed out elsewhere in this thread, it compiles correctly when there's no destructor. Essentially, this bug is caused by the context pointer being typed as void*, and becoming (of course) const(void*) for a const(S). If it'd been const(void)* in the first place, Shachar's code would have compiled and worked correctly. Is it misleading for the context pointer to be const(void)*? In a way, maybe. However, it's opaquely typed, and its constness says nothing about what's on the other end. Also, the language completely disregards the constness in any case: unittest { int i = 0; struct S { int n; void fun() const { i++; } } const S s; assert(i == 0); s.fun(); assert(i == 1); } -- Simen
Mar 06 2018
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 3/6/18 8:42 AM, Simen Kjærås wrote:

 It's a bug. As pointed out elsewhere in this thread, it compiles 
 correctly when there's no destructor. Essentially, this bug is caused by 
 the context pointer being typed as void*, and becoming (of course) 
 const(void*) for a const(S). If it'd been const(void)* in the first 
 place, Shachar's code would have compiled and worked correctly.
 
 Is it misleading for the context pointer to be const(void)*? In a way, 
 maybe. However, it's opaquely typed, and its constness says nothing 
 about what's on the other end. Also, the language completely disregards 
 the constness in any case:
 
 unittest {
      int i = 0;
      struct S {
          int n;
          void fun() const {
              i++;
          }
      }
      const S s;
      assert(i == 0);
      s.fun();
      assert(i == 1);
 }
That, I would consider a bug. If it's not, then definitely, you should be able to implicitly cast to/from const. So a bug report is in order. It should be decided one way or another -- either the context pointer is part of the struct type or it isn't. -Steve
Mar 06 2018
next sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 3/6/18 8:56 AM, Steven Schveighoffer wrote:
 On 3/6/18 8:42 AM, Simen Kjærås wrote:
 
 It's a bug. As pointed out elsewhere in this thread, it compiles 
 correctly when there's no destructor. Essentially, this bug is caused 
 by the context pointer being typed as void*, and becoming (of course) 
 const(void*) for a const(S). If it'd been const(void)* in the first 
 place, Shachar's code would have compiled and worked correctly.

 Is it misleading for the context pointer to be const(void)*? In a way, 
 maybe. However, it's opaquely typed, and its constness says nothing 
 about what's on the other end. Also, the language completely 
 disregards the constness in any case:

 unittest {
      int i = 0;
      struct S {
          int n;
          void fun() const {
              i++;
          }
      }
      const S s;
      assert(i == 0);
      s.fun();
      assert(i == 1);
 }
That, I would consider a bug. If it's not, then definitely, you should be able to implicitly cast to/from const. So a bug report is in order. It should be decided one way or another -- either the context pointer is part of the struct type or it isn't.
There is a third possibility: It's part of the type AND it's typed as const if it can be (i.e. none of the methods in the struct modify the context data, and any use of the context data implicitly casts from const). -Steve
Mar 06 2018
next sibling parent reply Shachar Shemesh <shachar weka.io> writes:
On 06/03/18 16:06, Steven Schveighoffer wrote:
 On 3/6/18 8:56 AM, Steven Schveighoffer wrote:
 So a bug report is in order. It should be decided one way or another 
 -- either the context pointer is part of the struct type or it isn't.
There is a third possibility: It's part of the type AND it's typed as const if it can be (i.e. none of the methods in the struct modify the context data, and any use of the context data implicitly casts from const). -Steve
struct is), for the same reason that const structs can modify static variables - it's not part of the struct. I'll phrase is another way, if this shouldn't compile: unittest { int i; struct S { int a; void func() const { ++i; } } } Then neither should this: struct S { static int i; int a; void func() const { ++i; } } I fail to see any reasoning[1] that disallows the former but allows the later. Shachar 1 - That is obviously not true. I see the reasoning all too well. Static vars are like globals, and you're not getting to them via the struct's instance. I would argue that, as far as the programmer, however, there is no difference. There is no semantic difference between allowing the first and not the second.
Mar 06 2018
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 3/6/18 9:42 AM, Shachar Shemesh wrote:

 I fail to see any reasoning[1] that disallows the former but allows the 
 later.
 
 
 1 - That is obviously not true. I see the reasoning all too well. Static 
 vars are like globals, and you're not getting to them via the struct's 
 instance. I would argue that, as far as the programmer, however, there 
 is no difference. There is no semantic difference between allowing the 
 first and not the second.
These aren't globals or static, they are no different than pointers (which is actually what they are). Here is the problem with option 2: If you have a struct that is immutable (or shared), it can be passed without problems to another thread. If you can then start mucking with local variables in another thread with the assumption that they are thread-local, then you have broken the guarantee of immutable or shared. Even const can cause problems. static data is different, in that it is either shared or thread-local, and then the path forward is quite clear. Passing thread-local data to another thread as if it were local to the recipient is like __gshared, and shouldn't be so easy to do. -Steve
Mar 06 2018
prev sibling parent Shachar Shemesh <shachar weka.io> writes:
Filed issue 18563

Shachar
Mar 06 2018
prev sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 6 March 2018 at 13:56:30 UTC, Steven Schveighoffer 
wrote:
 On 3/6/18 8:42 AM, Simen Kjærås wrote:
 unittest {
      int i = 0;
      struct S {
          int n;
          void fun() const {
              i++;
          }
      }
      const S s;
      assert(i == 0);
      s.fun();
      assert(i == 1);
 }
That, I would consider a bug. If it's not, then definitely, you should be able to implicitly cast to/from const. So a bug report is in order. It should be decided one way or another -- either the context pointer is part of the struct type or it isn't.
immutable throws a wrench in the works for the idea that the context pointer is part of the struct. Consider the exact same example, but with immutable(S) instead of const(S). IMO, this indicates the context is not part of the struct (though the context *pointer* arguably is). And just in case anyone doesn't immediately see how even disallowing the above would not solve the immutable problem: unittest { int n = 0; struct S { int fun() const { return n; } } immutable S s; assert(s.fun == 0); n++; assert(s.fun == 1); // Not so immutable now, are you? } Interestingly, replacing 'const' with 'immutable' on fun gives a compilation error: "immutable function 'foo.__unittest_foo_1_0.S.fun' cannot access mutable data 'n'". This seems even weirder to me, but can certainly be taken as evidence in favor of your view. The same error message does *not* show up if n is instead a global variable. -- Simen
Mar 06 2018
next sibling parent Shachar Shemesh <shachar weka.io> writes:
On 06/03/18 17:00, Simen Kjærås wrote:
 Interestingly, replacing 'const' with 'immutable' on fun gives a 
 compilation error: "immutable function 'foo.__unittest_foo_1_0.S.fun' 
 cannot access mutable data 'n'".
This just means it is completely and totally broken. Changing the "const" to "immutable" merely decreases the types that can be passed to the function. I see no case where it is legitimate for a const decoration to compile and an immutable one not. Shachar
Mar 06 2018
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 3/6/18 10:00 AM, Simen Kjærås wrote:
 On Tuesday, 6 March 2018 at 13:56:30 UTC, Steven Schveighoffer wrote:
 On 3/6/18 8:42 AM, Simen Kjærås wrote:
 unittest {
      int i = 0;
      struct S {
          int n;
          void fun() const {
              i++;
          }
      }
      const S s;
      assert(i == 0);
      s.fun();
      assert(i == 1);
 }
That, I would consider a bug. If it's not, then definitely, you should be able to implicitly cast to/from const. So a bug report is in order. It should be decided one way or another -- either the context pointer is part of the struct type or it isn't.
immutable throws a wrench in the works for the idea that the context pointer is part of the struct. Consider the exact same example, but with immutable(S) instead of const(S). IMO, this indicates the context is not part of the struct (though the context *pointer* arguably is). And just in case anyone doesn't immediately see how even disallowing the above would not solve the immutable problem: unittest {     int n = 0;     struct S {         int fun() const { return n; }     }     immutable S s;     assert(s.fun == 0);     n++;     assert(s.fun == 1); // Not so immutable now, are you? }
That's not a demonstration of breaking immutability. counter-proof: struct S { static int x; int fun() const { return x; } } immutable S s; assert(s.fun == 0); S.x++; assert(s.fun == 1); In other words, if the struct can access the data considered "outside it's instance", then it it can return it, change it even. However, this is not a question that has been answered yet. The compiler clearly treats the context pointer as part of the instance in some cases, and not part of the instance in other cases. Note that if the decision comes down that the context pointer is part of the instance, even *creating* an immutable S should be disallowed, as it clearly allows both mutable and immutable references.
 Interestingly, replacing 'const' with 'immutable' on fun gives a 
 compilation error: "immutable function 'foo.__unittest_foo_1_0.S.fun' 
 cannot access mutable data 'n'".
Wat... This is like taking both sides at once (it's both part of the instance and not part of the instance).
 This seems even weirder to me, but can certainly be taken as evidence in 
 favor of your view. The same error message does *not* show up if n is 
 instead a global variable.
This is very broken. And it needs attention. -Steve
Mar 06 2018
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06.03.2018 17:19, Steven Schveighoffer wrote:
 unittest {
      int n = 0;
      struct S {
          int fun() const { return n; }
      }
      immutable S s;
      assert(s.fun == 0);
      n++;
      assert(s.fun == 1); // Not so immutable now, are you?
 }
That's not a demonstration of breaking immutability. counter-proof: struct S {    static int x;    int fun() const { return x; } } immutable S s; assert(s.fun == 0); S.x++; assert(s.fun == 1); In other words, if the struct can access the data considered "outside it's instance", then it it can return it, change it even.
Here's how to break immutability: void main(){ int i = 0; struct S{ const(int)* fun()const pure{ return &i; } } immutable S s; static const(int)* foo(immutable(S) s)pure{ return s.fun(); } immutable(int) *pi=foo(s); import std.stdio; writeln(*pi); // 0 i+=1; writeln(*pi); // 1 } The reasoning "the reference is stored in the type yet not part of the type" does not work for pure functions, as then you cannot offer an alternative explanation in terms of an external data store. https://issues.dlang.org/show_bug.cgi?id=18567
Mar 06 2018
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 3/6/18 5:27 PM, Timon Gehr wrote:
 The reasoning "the reference is stored in the type yet not part of the 
 type" does not work for pure functions, as then you cannot offer an 
 alternative explanation in terms of an external data store.
Yeah, the more I think about it, the more I think the context pointer simply needs to be part of the instance in the type system. I posted a threading issue elsewhere. The problem is, correcting the bug will break a lot of code. -Steve
Mar 07 2018