Jump to page: 1 2
Thread overview
Help needed: immutable struct is a type modifier, and that's wrong and broken
Mar 13, 2020
FeepingCreature
Workaround: `unqualify(T)`: Now Look What You Made Me Do
Mar 13, 2020
FeepingCreature
Mar 13, 2020
Jacob Carlborg
Mar 13, 2020
FeepingCreature
Mar 13, 2020
Jacob Carlborg
Apr 15, 2020
FeepingCreature
Apr 16, 2020
RazvanN
Apr 16, 2020
Timon Gehr
Apr 16, 2020
RazvanN
Apr 16, 2020
RazvanN
Apr 16, 2020
Timon Gehr
March 13, 2020
See this bug: https://issues.dlang.org/show_bug.cgi?id=20670

immutable struct S { }

static if(is(S == immutable T, T)) {
    static assert(is(S == T));
}

So what happens here is that the spec states that immutable struct S is "the same as if every member had been marked immutable." But that's not at all what the compiler actually does: apparently, it just stores S with an immutable modifier.

As the code shows, immutable modifiers can be stripped away. This is of course bad, because it means that there's a type "S", but there's also a type "mutable S" that can only be created by template specialization or Unqual, and it can't be assigned to S despite nominally being the exact same type.

Help?

(For now, I'll probably homebrew an Unqual that uses mixin to figure out when a type has a "fake immutable modifier". But I won't like it.)

March 13, 2020
https://gist.github.com/FeepingCreature/61d1ead8b744cfa2152849e5b6680bb2

Here's a workaround for the bug. It's a version of `Unqual` that uses a completely different mechanism to get at the qualifier-less type: it looks up a symbol by that name in the surrounding aggregate, or tries to jump to the "parent of the first member".

Even though it "works" for our usecases, this clearly does not spark joy.

March 13, 2020
On Friday, 13 March 2020 at 09:33:18 UTC, FeepingCreature wrote:
> See this bug: https://issues.dlang.org/show_bug.cgi?id=20670
>
> immutable struct S { }
>
> static if(is(S == immutable T, T)) {
>     static assert(is(S == T));
> }
>
> So what happens here is that the spec states that immutable struct S is "the same as if every member had been marked immutable." But that's not at all what the compiler actually does: apparently, it just stores S with an immutable modifier.
>
> As the code shows, immutable modifiers can be stripped away. This is of course bad, because it means that there's a type "S", but there's also a type "mutable S" that can only be created by template specialization or Unqual, and it can't be assigned to S despite nominally being the exact same type.
>
> Help?
>
> (For now, I'll probably homebrew an Unqual that uses mixin to figure out when a type has a "fake immutable modifier". But I won't like it.)

import std;

immutable struct S { }

void main()
{
    Unqual!S s;
    Unqual!S s2;

    pragma(msg, typeof(s)); // will print S
    s = s2;
}

The above code will compile, but if you add a member to S, it fails:

Error: cannot modify struct instance `s` of type `S` because it contains `const` or `immutable` members

Even though it looks like it's possible to strip of the immutable qualifier it will actually not work in practice. If you don't have any members, it doesn't really matter. If you do have members, it will not compile.

--
/Jacob Carlborg
March 13, 2020
On Friday, 13 March 2020 at 13:25:52 UTC, Jacob Carlborg wrote:
> import std;
>
> immutable struct S { }
>
> void main()
> {
>     Unqual!S s;
>     Unqual!S s2;
>
>     pragma(msg, typeof(s)); // will print S
>     s = s2;
> }
>
> The above code will compile, but if you add a member to S, it fails:
>
> Error: cannot modify struct instance `s` of type `S` because it contains `const` or `immutable` members
>
> Even though it looks like it's possible to strip of the immutable qualifier it will actually not work in practice. If you don't have any members, it doesn't really matter. If you do have members, it will not compile.
>
> --
> /Jacob Carlborg

Okay, so you're saying there's an immutable modifier on S, but it doesn't do anything because all the fields are immutable too?

So the actual problem would be https://issues.dlang.org/show_bug.cgi?id=20671 , where the immutable-stripped type is just different enough to prevent array conversion.

In that case, isn't the solution just to ditch the extraneous implicit immutable() entirely? I mean, either it should always be on S or it should never be on S.

March 13, 2020
On Friday, 13 March 2020 at 14:17:45 UTC, FeepingCreature wrote:

> Okay, so you're saying there's an immutable modifier on S, but it doesn't do anything because all the fields are immutable too?

Well, it looks like the spec is kind of correct:

"A struct declaration can have a storage class of const, immutable or shared. It has an equivalent effect as declaring each member of the struct as const, immutable or shared"

What happens if the struct doesn't have any members? If there are no members that can be immutable then the struct can't be immutable?

I don't know.

--
/Jacob Carlborg
April 15, 2020
On Friday, 13 March 2020 at 14:49:49 UTC, Jacob Carlborg wrote:
> On Friday, 13 March 2020 at 14:17:45 UTC, FeepingCreature wrote:
>
>> Okay, so you're saying there's an immutable modifier on S, but it doesn't do anything because all the fields are immutable too?
>
> Well, it looks like the spec is kind of correct:
>
> "A struct declaration can have a storage class of const, immutable or shared. It has an equivalent effect as declaring each member of the struct as const, immutable or shared"
>
> What happens if the struct doesn't have any members? If there are no members that can be immutable then the struct can't be immutable?
>
> I don't know.
>
> --
> /Jacob Carlborg

Late answer: the problem is that the storage class *doesn't* just declare each member as immutable. If you declare

```
immutable struct S { int i; }
```

then this is *not* the same as

```
struct S { immutable int i; }
```

but rather something that behaves similar to

```
private struct S_ { immutable int i; }
alias S = immutable(S_);
```

because the compiler can't differentiate between `immutable struct S` and `immutable(S)`.

Which results in the problem that `Unqual!S` yields `S_`, which is a type that isn't supposed to exist, or at least not in a end-user visible way.

April 16, 2020
On Friday, 13 March 2020 at 09:33:18 UTC, FeepingCreature wrote:
> See this bug: https://issues.dlang.org/show_bug.cgi?id=20670
>
> immutable struct S { }
>
> static if(is(S == immutable T, T)) {
>     static assert(is(S == T));
> }
>
> So what happens here is that the spec states that immutable struct S is "the same as if every member had been marked immutable." But that's not at all what the compiler actually does: apparently, it just stores S with an immutable modifier.
>
> As the code shows, immutable modifiers can be stripped away. This is of course bad, because it means that there's a type "S", but there's also a type "mutable S" that can only be created by template specialization or Unqual, and it can't be assigned to S despite nominally being the exact same type.
>
> Help?
>
> (For now, I'll probably homebrew an Unqual that uses mixin to figure out when a type has a "fake immutable modifier". But I won't like it.)

There is a difference between defining a struct as immutable (e.g. immutable struct A {}) and declaring an immutable instance of a struct (e.g. immutable S a;). In the first case the immutable acts as a storage class specifier whereas in the second one it is a type constructor. Unqual is used to ditch only type constructors not storage class specifiers. In the case of storage class specifiers there is no way to get rid of them because, as you mentioned, there is no associated type. So from this perspective, I think that [1] should be fixed.

Additionally, I think that

static if(is(S == immutable T, T))

should also not pass.

The correct form should be

static if(is(S == immutable))

This way you express the fact that S is an immutably defined type (with a storage specifier), whereas in the first case you test if S is an immutably declared type (with a type constructor).

[1]https://issues.dlang.org/show_bug.cgi?id=20670
April 16, 2020
On 16.04.20 05:55, RazvanN wrote:
> 
> 
> Additionally, I think that
> 
> static if(is(S == immutable T, T))
> 
> should also not pass.
> 
> The correct form should be
> 
> static if(is(S == immutable))
> 
> This way you express the fact that S is an immutably defined type (with a storage specifier), whereas in the first case you test if S is an immutably declared type (with a type constructor).

So the proposal is to turn `is` into an even more convoluted mess? :)

Why should it even be possible to test for `immutable` on the declaration? That seems like an implementation detail as the only thing it achieves is that all members are marked that way.
April 16, 2020
On Thursday, 16 April 2020 at 04:28:35 UTC, Timon Gehr wrote:
> On 16.04.20 05:55, RazvanN wrote:
>> 
>> 
>> Additionally, I think that
>> 
>> static if(is(S == immutable T, T))
>> 
>> should also not pass.
>> 
>> The correct form should be
>> 
>> static if(is(S == immutable))
>> 
>> This way you express the fact that S is an immutably defined type (with a storage specifier), whereas in the first case you test if S is an immutably declared type (with a type constructor).
>
> So the proposal is to turn `is` into an even more convoluted mess? :)
>
> Why should it even be possible to test for `immutable` on the declaration? That seems like an implementation detail as the only thing it achieves is that all members are marked that way.

I agree that this is kind of awkward, but things are confusing otherwise.

Do you agree that in this situation `static if (is(S == immutable T, T))` should not pass? Or should it pass and the `static assert(is(S == T))` should also pass? In that case, T should bind to an empty type, which D, as far as I know does not support at the moment.
April 16, 2020
On Thursday, 16 April 2020 at 06:05:12 UTC, RazvanN wrote:
> On Thursday, 16 April 2020 at 04:28:35 UTC, Timon Gehr wrote:
>> On 16.04.20 05:55, RazvanN wrote:
>>> 
>>> 
>>> Additionally, I think that
>>> 
>>> static if(is(S == immutable T, T))
>>> 
>>> should also not pass.
>>> 
>>> The correct form should be
>>> 
>>> static if(is(S == immutable))
>>> 
>>> This way you express the fact that S is an immutably defined type (with a storage specifier), whereas in the first case you test if S is an immutably declared type (with a type constructor).
>>
>> So the proposal is to turn `is` into an even more convoluted mess? :)
>>
>> Why should it even be possible to test for `immutable` on the declaration? That seems like an implementation detail as the only thing it achieves is that all members are marked that way.

Oh I see your point now. In the above example S and T are the same type because `immutable immutable S` == `immutable S`. Right, but the fact that an immutably defined struct cannot be stripped of it's qualifier brings some complications here.

Anyway, the fact that T is not equal to S is definitely a bug in this situation.
« First   ‹ Prev
1 2