Thread overview
Class qualifier vs struct qualifier
Jun 13, 2018
RazvanN
Jun 13, 2018
Jonathan M Davis
Jun 14, 2018
RazvanN
Jun 14, 2018
Jonathan M Davis
Jun 16, 2018
Timoses
Jun 16, 2018
Jonathan M Davis
Jun 13, 2018
Jonathan M Davis
Jun 15, 2018
David Bennett
June 13, 2018
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.
June 13, 2018
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
June 13, 2018
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

June 13, 2018
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

June 14, 2018
> 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


June 14, 2018
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

June 15, 2018
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.
June 16, 2018
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..?
June 16, 2018
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