www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Class qualifier vs struct qualifier

reply RazvanN <razvan.nitu1305 gmail.com> writes:
Hello,

I'm having a hard time understanding whether this inconsistency 
is a bug or intended behavior:

immutable class Foo {}
immutable struct Bar {}

void main()
{
     import std.stdio : writeln;
     Foo a;
     Bar b;

     writeln("typeof(a): ", typeof(a).stringof);
     writeln("typeof(b): ", typeof(b).stringof);
}

prints:

typeof(Foo): Foo
typeof(Bar): immutable(Bar)


It seems like the class storage class is not taken into account 
which leads to some awkward situations like:

immutable class Foo
{
     this() {}
}

void main()
{
     Foo a = new Foo(); // error: immutable method `this` is not 
callable using a
                        // mutable object
}

To make it work I have to add immutable to both sides of the 
expression : immutable Foo a = new immutable Foo(); this is a 
wonder of redundancy. I already declared the class as immutable 
so it shouldn't be possible to have mutable instances of it (and 
it isn't), however I am forced to write the immutable twice even 
though it is pretty obvious that the class cannot be mutated.
Jun 13 2018
next sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 6/13/18 3:35 AM, RazvanN wrote:
 Hello,
 
 I'm having a hard time understanding whether this inconsistency is a bug 
 or intended behavior:
 
 immutable class Foo {}
 immutable struct Bar {}
 
 void main()
 {
      import std.stdio : writeln;
      Foo a;
      Bar b;
 
      writeln("typeof(a): ", typeof(a).stringof);
      writeln("typeof(b): ", typeof(b).stringof);
 }
 
 prints:
 
 typeof(Foo): Foo
 typeof(Bar): immutable(Bar)
 
 
 It seems like the class storage class is not taken into account which 
 leads to some awkward situations like:
 
 immutable class Foo
 {
      this() {}
 }
 
 void main()
 {
      Foo a = new Foo(); // error: immutable method `this` is not 
 callable using a
                         // mutable object
 }
 
 To make it work I have to add immutable to both sides of the expression 
 : immutable Foo a = new immutable Foo(); this is a wonder of redundancy. 
 I already declared the class as immutable so it shouldn't be possible to 
 have mutable instances of it (and it isn't), however I am forced to 
 write the immutable twice even though it is pretty obvious that the 
 class cannot be mutated.
Just on the principle of least surprise, I'd call this a bug. I don't know what the intention is, but if the intention is for this behavior, we should re-visit. -Steve
Jun 13 2018
prev sibling next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Wednesday, June 13, 2018 07:35:25 RazvanN via Digitalmars-d-learn wrote:
 Hello,

 I'm having a hard time understanding whether this inconsistency
 is a bug or intended behavior:

 immutable class Foo {}
 immutable struct Bar {}

 void main()
 {
      import std.stdio : writeln;
      Foo a;
      Bar b;

      writeln("typeof(a): ", typeof(a).stringof);
      writeln("typeof(b): ", typeof(b).stringof);
 }

 prints:

 typeof(Foo): Foo
 typeof(Bar): immutable(Bar)


 It seems like the class storage class is not taken into account
 which leads to some awkward situations like:

 immutable class Foo
 {
      this() {}
 }

 void main()
 {
      Foo a = new Foo(); // error: immutable method `this` is not
 callable using a
                         // mutable object
 }

 To make it work I have to add immutable to both sides of the
 expression : immutable Foo a = new immutable Foo(); this is a
 wonder of redundancy. I already declared the class as immutable
 so it shouldn't be possible to have mutable instances of it (and
 it isn't), however I am forced to write the immutable twice even
 though it is pretty obvious that the class cannot be mutated.
Honestly, from what I understand of how this works, what I find weird is the struct case. immutable on classes does _not_ make the class itself immutable. It just makes all of its members immutable - hence the error about trying to allocate new Foo instead of new immutable Foo. So, that is exactly what I would expect. And honestly, being able to write Foo and have it imply immutable Foo would get _really_ confusing when reading and debugging code. What's bizarre is that marking the struct with immutable would affect anything other than its members. Bar b; should not claim that typeof(b) is immutable(Bar). b was not marked as immutable. It was listed as Bar, not immutable Bar. So, b shouldn't be immutable. - Jonathan M Davis
Jun 13 2018
parent reply RazvanN <razvan.nitu1305 gmail.com> writes:
 Honestly, from what I understand of how this works, what I find 
 weird is the struct case. immutable on classes does _not_ make 
 the class itself immutable. It just makes all of its members 
 immutable - hence the error about trying to allocate new Foo 
 instead of new immutable Foo. So, that is exactly what I would 
 expect. And honestly, being able to write Foo and have it imply 
 immutable Foo would get _really_ confusing when reading and 
 debugging code.
Maybe it has something to do with structs being value types and classes being reference types. If you declare an immutable struct then you cannot modify it's value, while if you declare a class, the pointer to the class is mutable, while the class fields are not. This kind of makes sense, however the thing is that in both situations you are not able to mutate any of the class/struct fields no matter how you instantiate it, so it is really redundant to use `immutable` when creating the instance.
 What's bizarre is that marking the struct with immutable would 
 affect anything other than its members.

 Bar b;

 should not claim that typeof(b) is immutable(Bar). b was not 
 marked as
 immutable. It was listed as Bar, not immutable Bar. So, b 
 shouldn't be
 immutable.

 - Jonathan M Davis
Jun 14 2018
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, June 14, 2018 08:39:48 RazvanN via Digitalmars-d-learn wrote:
 Honestly, from what I understand of how this works, what I find
 weird is the struct case. immutable on classes does _not_ make
 the class itself immutable. It just makes all of its members
 immutable - hence the error about trying to allocate new Foo
 instead of new immutable Foo. So, that is exactly what I would
 expect. And honestly, being able to write Foo and have it imply
 immutable Foo would get _really_ confusing when reading and
 debugging code.
Maybe it has something to do with structs being value types and classes being reference types. If you declare an immutable struct then you cannot modify it's value, while if you declare a class, the pointer to the class is mutable, while the class fields are not. This kind of makes sense, however the thing is that in both situations you are not able to mutate any of the class/struct fields no matter how you instantiate it, so it is really redundant to use `immutable` when creating the instance.
Except that it isn't redundant, because without using immutable, it looks like you're trying to create a mutable object. Would you honestly ever look at something like Foo foo; and expect the object to be immutable? And sadly, looking at the type declaration isn't even necessarily enough to know that it was marked with immutable, because someone could have done something dumb like put immutable: higher up in the file. Types normally require that you explicitly mark them as immutable to make them immutable when using them anywhere - including declaring variables or allocating objects. I don't see why having made all of the members of the type immutable should change that. It's just extra magic that makes it harder to read the code. Sure, it would save you a little bit of typing when you do something like auto foo = new Foo; if makes it immutable for you, but it's at the cost of code clarity. - Jonathan M Davis
Jun 14 2018
parent reply Timoses <timosesu gmail.com> writes:
On Thursday, 14 June 2018 at 17:07:09 UTC, Jonathan M Davis wrote:
 Sure, it would save you a little bit of typing when you do 
 something like

 auto foo = new Foo;

 if makes it immutable for you, but it's at the cost of code 
 clarity.
Why should it even? Isn't immutable class C { int a; } the same as class C { immutable { int a; } } ? Does the following code clarify why an instance if immutable struct HAS to be immutable while an instance of class does not have to be immutable?? immutable struct S {} immutable class C {} void main() { S sa = S(); pragma(msg, typeof(sa)); // immutable(S) S sb = S(); // sa = sb; // Error: cannot modify immutable expression sa C ca = new C(); pragma(msg, typeof(ca)); // C C cb = new C(); ca = cb; // works } Then the question would rather be why S s = S(); // immutable(S) does what it seems to be doing..?
Jun 16 2018
parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Saturday, June 16, 2018 07:13:28 Timoses via Digitalmars-d-learn wrote:
 On Thursday, 14 June 2018 at 17:07:09 UTC, Jonathan M Davis wrote:
 Sure, it would save you a little bit of typing when you do
 something like

 auto foo = new Foo;

 if makes it immutable for you, but it's at the cost of code
 clarity.
Why should it even? Isn't immutable class C { int a; } the same as class C { immutable { int a; } } ? Does the following code clarify why an instance if immutable struct HAS to be immutable while an instance of class does not have to be immutable?? immutable struct S {} immutable class C {} void main() { S sa = S(); pragma(msg, typeof(sa)); // immutable(S) S sb = S(); // sa = sb; // Error: cannot modify immutable expression sa C ca = new C(); pragma(msg, typeof(ca)); // C C cb = new C(); ca = cb; // works } Then the question would rather be why S s = S(); // immutable(S) does what it seems to be doing..?
It's perfectly legal to do struct S { int i; immutable int j; } and, declaring a variable of that type S s; does not result in the type of s being treated as immutable - just the members. Similarly, struct S { immutable int i; } S s; does not result in s being immutable(S). It's just when the struct itself is marked as immutable that every variable of that type is suddenly treated as immutable as well. And of course, with classes, marking the class as immutable is identical to marking all of its members as immutable. Why that's not the case for structs, I have no idea. Regardless, I think that it's a terrible idea to implicitly make a type immutable everywhere. If I see S s; I expect S to be mutable. Sure, it could have const or immutable members (much as that's generally a terrible idea for structs, because it makes them non-assignable and potentially non-copyable), but I would never expect the type to be immutable(S) when the variable is clearly typed as S - just like I wouldn't expect new S to result in an immutable(S). I was _very_ surprised to see that the compile treats immutable struct S { } differently from struct S { immutable: } and I really think that it should be fixed so that it doesn't. The fact that S s; could ever result in the variable being anything other than S most definitely breaks the principle of least surprise, and it doesn't match how the rest of the language works. It's particularly bizarre when you consider that it doesn't happen when all of the members are immutable if the struct wasn't directly marked with immutable. IMHO, even if a type were unusable as anything other than immutable, variables of that type should still have to be marked with immutable, otherwise the variable declaration doesn't match the actual type of the variable, which seems like a terrible idea. - Jonathan M Davis
Jun 16 2018
prev sibling next sibling parent Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Wednesday, June 13, 2018 14:33:48 Jonathan M Davis via Digitalmars-d-
learn wrote:
 On Wednesday, June 13, 2018 07:35:25 RazvanN via Digitalmars-d-learn 
wrote:
 Hello,

 I'm having a hard time understanding whether this inconsistency
 is a bug or intended behavior:

 immutable class Foo {}
 immutable struct Bar {}

 void main()
 {

      import std.stdio : writeln;
      Foo a;
      Bar b;

      writeln("typeof(a): ", typeof(a).stringof);
      writeln("typeof(b): ", typeof(b).stringof);

 }

 prints:

 typeof(Foo): Foo
 typeof(Bar): immutable(Bar)


 It seems like the class storage class is not taken into account
 which leads to some awkward situations like:

 immutable class Foo
 {

      this() {}

 }

 void main()
 {

      Foo a = new Foo(); // error: immutable method `this` is not

 callable using a

                         // mutable object

 }

 To make it work I have to add immutable to both sides of the
 expression : immutable Foo a = new immutable Foo(); this is a
 wonder of redundancy. I already declared the class as immutable
 so it shouldn't be possible to have mutable instances of it (and
 it isn't), however I am forced to write the immutable twice even
 though it is pretty obvious that the class cannot be mutated.
Honestly, from what I understand of how this works, what I find weird is the struct case. immutable on classes does _not_ make the class itself immutable. It just makes all of its members immutable - hence the error about trying to allocate new Foo instead of new immutable Foo. So, that is exactly what I would expect. And honestly, being able to write Foo and have it imply immutable Foo would get _really_ confusing when reading and debugging code. What's bizarre is that marking the struct with immutable would affect anything other than its members. Bar b; should not claim that typeof(b) is immutable(Bar). b was not marked as immutable. It was listed as Bar, not immutable Bar. So, b shouldn't be immutable.
https://issues.dlang.org/show_bug.cgi?id=18977 - Jonathan M Davis
Jun 13 2018
prev sibling parent David Bennett <davidbennett bravevision.com> writes:
On Wednesday, 13 June 2018 at 07:35:25 UTC, RazvanN wrote:
 Hello,

 I'm having a hard time understanding whether this inconsistency 
 is a bug or intended behavior:

 immutable class Foo {}
 immutable struct Bar {}

 void main()
 {
     import std.stdio : writeln;
     Foo a;
     Bar b;

     writeln("typeof(a): ", typeof(a).stringof);
     writeln("typeof(b): ", typeof(b).stringof);
 }

 prints:

 typeof(Foo): Foo
 typeof(Bar): immutable(Bar)


 It seems like the class storage class is not taken into account 
 which leads to some awkward situations like:

 immutable class Foo
 {
     this() {}
 }

 void main()
 {
     Foo a = new Foo(); // error: immutable method `this` is not 
 callable using a
                        // mutable object
 }

 To make it work I have to add immutable to both sides of the 
 expression : immutable Foo a = new immutable Foo(); this is a 
 wonder of redundancy. I already declared the class as immutable 
 so it shouldn't be possible to have mutable instances of it 
 (and it isn't), however I am forced to write the immutable 
 twice even though it is pretty obvious that the class cannot be 
 mutated.
Just tested and I only seem to need to add the immutable to the left of the expression. https://run.dlang.io/is/EZ7es0 Also with the struct `Bar* b = new Bar();` works fine so i guess the discrepancy is because the class ref is mutatable.
Jun 14 2018