September 01, 2023

What does the compiler generated opAssign for a struct look like?

Let's say I have a struct, which has at least one const member.

struct S
{
    this(int some_a, int some_b)
    {
        a = some_a;
        b = some_b;
    }
    int a;
    const int b;
}

If I then try to assign to it, the compiler complains that struct instances with const members cannot be be modified.

    auto s = S(1, 2);
    s = S(3, 4);

Full example here: https://run.dlang.io/is/ssT12m

This makes sense to me if I understand the compiler to be generating a member-by-member assignment operator like in C++, but the description in Programming in D states that in D (Assignment Operator section here) the compiler generated function will create a temporary copy of the right-hand-side, and then replace the left-hand-side with that temporary copy. To me that description implies that const members should be ok when it comes to assigning to the entire object.

In my specific encounter with this scenario, I'm dealing with core.stdcpp.string_view so I can't modify the definition to remove the const member. Also with an old version of ldc I have on my linux machine this seems to work, though I'm not able to replicate that with the "all dmd versions" option on run.dlang.io (where they all fail to compile).

September 01, 2023
On Friday, September 1, 2023 6:46:33 PM BST Christopher Winter via Digitalmars-d wrote:
> What does the compiler generated opAssign for a struct look like?
>
> Let's say I have a struct, which has at least one `const` member.
>
> ```
> struct S
> {
>      this(int some_a, int some_b)
>      {
>          a = some_a;
>          b = some_b;
>      }
>      int a;
>      const int b;
> }
> ```
>
> If I then try to assign to it, the compiler complains that struct instances with const members cannot be be modified.
>
>
> ```
>      auto s = S(1, 2);
>      s = S(3, 4);
> ```
>
> Full example here: https://run.dlang.io/is/ssT12m
>
> This makes sense to me if I understand the compiler to be generating a member-by-member assignment operator like in C++, but the description in Programming in D states that in D (Assignment Operator section [here](http://ddili.org/ders/d.en/special_functions.html)) the compiler generated function will create a temporary copy of the right-hand-side, and then replace the left-hand-side with that temporary copy. To me that description implies that const members should be ok when it comes to assigning to the entire object.
>
> In my specific encounter with this scenario, I'm dealing with `core.stdcpp.string_view` so I can't modify the definition to remove the const member. Also with an old version of ldc I have on my linux machine this seems to work, though I'm not able to replicate that with the "all dmd versions" option on run.dlang.io (where they all fail to compile).

It doesn't matter whether the members are assigned to individually or if a mutated copy is blitted onto the original. Either would involve mutating const which would violate the type system. Unlike C++, D's const actually means const and does not have backdoors. If any version of any D compiler allows you to ever mutate const, it's a bug. And casting away const to mutate an object is undefined behavior, because it violates the type system.

Const objects can only be modified via mutable references/pointers pointing to the same object, which wouldn't be legal with value types like int, and obtaining such a mutable reference with any type cannot be done via casting without violating the type system.

So, in general, having const or immutable member variables in a struct is a terrible idea in D precisely because it's not legal to assign to them.

I can't speak to what's being done with core.stdcpp.string_view, because I've never used it, but if you're dealing with a type that you do not control and thus cannot avoid having a const or immutable member, then it's simply not going to be legal to assign to any variable of that type.

- Jonathan M Davis