Thread overview
Immutability and arrays
Dec 14, 2021
rumbu
Dec 14, 2021
WebFreak001
Dec 14, 2021
Stanislav Blinov
Dec 14, 2021
rumbu
Dec 14, 2021
Stanislav Blinov
Dec 15, 2021
rumbu
December 14, 2021

I am trying to understand why in this two different cases (Simple and Complex), the compiler behaviour is different.

struct SimpleStruct { int x;}
struct ComplexStruct { int[] x; }

void main()
{
    SimpleStruct[] buf1;
    immutable(SimpleStruct)[] ibuf1;
    buf1[0 .. 10] = ibuf1[0 .. 10];
    //this works

    ComplexStruct[] buf2;
    immutable(ComplexStruct)[] ibuf2;

    buf2[0 .. 10] = ibuf2[0 .. 10];
    //error cannot implicitly convert expression `ibuf2[0..10]` of type `immutable(ComplexStruct)[]` to `ComplexStruct[]`
}
December 14, 2021

On Tuesday, 14 December 2021 at 08:44:02 UTC, rumbu wrote:

>

I am trying to understand why in this two different cases (Simple and Complex), the compiler behaviour is different.

struct SimpleStruct { int x;}
struct ComplexStruct { int[] x; }

void main()
{
    SimpleStruct[] buf1;
    immutable(SimpleStruct)[] ibuf1;
    buf1[0 .. 10] = ibuf1[0 .. 10];
    //this works

    ComplexStruct[] buf2;
    immutable(ComplexStruct)[] ibuf2;

    buf2[0 .. 10] = ibuf2[0 .. 10];
    //error cannot implicitly convert expression `ibuf2[0..10]` of type `immutable(ComplexStruct)[]` to `ComplexStruct[]`
}

there are special cases in the compiler for values that have no mutable indirections: https://dlang.org/spec/const3.html#implicit_qualifier_conversions

>

Values that have no mutable indirections (including structs that don't contain any field with mutable indirections) can be implicitly converted across mutable, const, immutable, const shared, inout and inout shared.

so that first struct may be implicitly converted between mutable/immutable/const because it doesn't contain any mutable indirections (mutable arrays/pointers/references)

December 14, 2021

On Tuesday, 14 December 2021 at 08:44:02 UTC, rumbu wrote:

>

I am trying to understand why in this two different cases (Simple and Complex), the compiler behaviour is different.

struct SimpleStruct { int x;}
struct ComplexStruct { int[] x; }

void main()
{
    SimpleStruct[] buf1;
    immutable(SimpleStruct)[] ibuf1;
    buf1[0 .. 10] = ibuf1[0 .. 10];
    //this works

    ComplexStruct[] buf2;
    immutable(ComplexStruct)[] ibuf2;

    buf2[0 .. 10] = ibuf2[0 .. 10];
    //error cannot implicitly convert expression `ibuf2[0..10]` of type `immutable(ComplexStruct)[]` to `ComplexStruct[]`
}

Because is(typeof(immutable(ComplexStruct).x) == immutable(int[])). Can't bind an array of immutable to array of mutable. This would require a deep copy, i.e. copy constructor.

December 14, 2021

On Tuesday, 14 December 2021 at 12:13:23 UTC, Stanislav Blinov wrote:

>

Because is(typeof(immutable(ComplexStruct).x) == immutable(int[])). Can't bind an array of immutable to array of mutable. This would require a deep copy, i.e. copy constructor.

This means that the only way to write a generic function which copies an array of immutable elements to another array is this:

    void copy10(T)(T[] dst, immutable(T)[] src)
    {
        static if (is(immutable(T): T))
        	dst[0..10] = src[0..10];
         else
            dst[0..10] = cast(T[])src[0..10]; // or better a deep copy
    }

Btw, tried to give ComplexStruct a some copy constructors (casting away immutable is just for the example, I know it's not the way to do it).

    struct ComplexStruct
    {
       int[] x;
    	
       this(ref return scope ComplexStruct another)
       {
           this.x = another.x;
       }

       this(ref return scope immutable(ComplexStruct) another)
       {
           this.x = cast(int[])(another.x);
       }
    }

Still slice assignment does not work. I think I will drop immutability, it's too complicated to work with.

December 14, 2021

On 12/14/21 3:44 AM, rumbu wrote:

>

I am trying to understand why in this two different cases (Simple and Complex), the compiler behaviour is different.

struct SimpleStruct { int x;}
struct ComplexStruct { int[] x; }

void main()
{
     SimpleStruct[] buf1;
     immutable(SimpleStruct)[] ibuf1;
     buf1[0 .. 10] = ibuf1[0 .. 10];
     //this works

     ComplexStruct[] buf2;
     immutable(ComplexStruct)[] ibuf2;

     buf2[0 .. 10] = ibuf2[0 .. 10];
     //error cannot implicitly convert expression `ibuf2[0..10]` of type `immutable(ComplexStruct)[]` to `ComplexStruct[]`
}

I know there have been several answers as to what the rules are, I want to answer why the rules are there.

In the first case, you have a simple struct which has a single integer in it. When copying that struct, you have no indirections (pointers), which means that adjusting the mutability is allowed:

immutable s = SimpleStruct(5);
SimpleStruct s2 = s; // ok, we are making a copy of everything

In the second case, the int[] contains a pointer. So if you made a copy of that, you cannot change the mutability of the type, because now it would have a mutable pointer to immutable data:

immutable s = ComplexStruct([5]);
ComplexStruct s2 = s; // error, cannot implicitly convert, there is an indirection

The WHY is this: let's say the above was allowed, s2 is mutable, which means s2.x is mutable. Now I can do:

s2.x[0] = 6;

And all of a sudden, immutable data has changed! This cannot be allowed, which is why you can't copy the struct.

All the other problems you are having are deriving from this problem.

-Steve

December 14, 2021

On Tuesday, 14 December 2021 at 15:28:30 UTC, Steven Schveighoffer wrote:

>

All the other problems you are having are deriving from this problem.

Not exactly. One of the problems seems to be a genuine bug:

struct S
{
    int[] x;

    // doesn't even participate here, neither would postblit
    this(ref return scope inout S other)
    {
        x = other.x.dup;
    }

    void opAssign(ref return scope inout S other)
    {
        x = other.x.dup;
    }
}

void main()
{
    immutable(S)[] src = [S([1, 2]), S([3, 4])];

    auto dst = new S[src.length];

    //dst[0 .. $] = src[0 .. $]; // this fails to compile even with opAssign defined

    // this works:
    foreach (i, ref it; dst)
        it = src[i];
}

Spec: https://dlang.org/spec/arrays.html#array-copying

>

...contents of the array are the target of the assignment...

Per that wording, slice assignment should perform the equivalent of that foreach loop (after overlap checks, etc.). It doesn't, just tries implicit conversion, and fails.

Now, since we have copy ctors, slice assignment should, ostensibly, attempt to copy-assign elements (i.e. absent opAssign, try the copy ctor first).

December 14, 2021

On 12/14/21 10:53 AM, Stanislav Blinov wrote:

>

Now, since we have copy ctors, slice assignment should, ostensibly, attempt to copy-assign elements (i.e. absent opAssign, try the copy ctor first).

I agree slice-assign should work here with a copy ctor. What I think is happening is that it's still very much built on memcpy + postblit (so much of the array runtime is still magic functions).

postblit doesn't work because it first makes a copy of the data AS-IS, which means it's still immutable, and only after the postblit would it be mutable.

-Steve

December 14, 2021

On 12/14/21 11:14 AM, Steven Schveighoffer wrote:

>

On 12/14/21 10:53 AM, Stanislav Blinov wrote:

>

Now, since we have copy ctors, slice assignment should, ostensibly, attempt to copy-assign elements (i.e. absent opAssign, try the copy ctor first).

I agree slice-assign should work here with a copy ctor. What I think is happening is that it's still very much built on memcpy + postblit (so much of the array runtime is still magic functions).

postblit doesn't work because it first makes a copy of the data AS-IS, which means it's still immutable, and only after the postblit would it be mutable.

Er... scratch that, this isn't construction, it should use opAssign. Again, probably because memcpy+postblit is used by the runtime.

If not reported, it should be.

-Steve

December 14, 2021

On 12/14/21 11:19 AM, Steven Schveighoffer wrote:

>

Er... scratch that, this isn't construction, it should use opAssign. Again, probably because memcpy+postblit is used by the runtime.

If not reported, it should be.

Simple proof that it is a bug:

immutable (ComplexStruct)[] arr;
ComplexStruct[] arr2;

arr2[0] = arr[0]; // ok
arr2[] = arr[]; // error

If you can copy one element, you should be able to copy all the elements.

-Steve

December 15, 2021

On Tuesday, 14 December 2021 at 16:21:03 UTC, Steven Schveighoffer wrote:

>

On 12/14/21 11:19 AM, Steven Schveighoffer wrote:

>

Er... scratch that, this isn't construction, it should use opAssign. Again, probably because memcpy+postblit is used by the runtime.

If not reported, it should be.

Simple proof that it is a bug:

immutable (ComplexStruct)[] arr;
ComplexStruct[] arr2;

arr2[0] = arr[0]; // ok
arr2[] = arr[]; // error

If you can copy one element, you should be able to copy all the elements.

-Steve

Thank you everybody, especially to Steve for the detailed explanation.

It seems that every post I put in the learn forum, results in a bug report :)

https://issues.dlang.org/show_bug.cgi?id=22601
https://issues.dlang.org/show_bug.cgi?id=22600