www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Immutability and arrays

reply rumbu <rumbu rumbu.ro> writes:
I am trying to understand why in this two different cases (Simple 
and Complex), the compiler behaviour is different.

```d
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[]`
}
```
Dec 14 2021
next sibling parent WebFreak001 <d.forum webfreak.org> writes:
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.

 ```d
 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)
Dec 14 2021
prev sibling next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
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.

 ```d
 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.
Dec 14 2021
parent rumbu <rumbu rumbu.ro> writes:
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: ```d 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). ```d 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.
Dec 14 2021
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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.
 
 ```d
 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: ```d 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: ```d 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: ```d 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
Dec 14 2021
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
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: ```d 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).
Dec 14 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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
Dec 14 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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
Dec 14 2021
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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: ```d 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
Dec 14 2021
parent rumbu <rumbu rumbu.ro> writes:
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: ```d 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
Dec 14 2021