www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Stupid question about AA. The following code works but I don't

reply Uranuz <neuranuz gmail.com> writes:
I am stupid today :) So I have a question. The piece of code 
given:

http://dpaste.dzfl.pl/523781df67ab

It looks good, but I don't understand why it works?
Apr 09 2016
parent reply ag0aep6g <anonymous example.com> writes:
On 09.04.2016 18:13, Uranuz wrote:
 http://dpaste.dzfl.pl/523781df67ab
For reference, the code: ---- import std.stdio; void main() { string[][string] mapka; string[]* mapElem = "item" in mapka; //Checking if I have item if( !mapElem ) mapElem = &( mapka["item"] = [] ); //Creating empty element inside map writeln( (*mapElem).capacity ); //Appending should reallocate, so pointer to array should change *mapElem ~= ["dog", "cat", "horse", "penguin", "fish", "frog"]; //But AA still somehow knows the right pointer writeln(mapka); //It works, but I dont understand why? } ---- mapElem is not a pointer to the elements of the array. It's a pointer to the dynamic array structure which holds the pointer to the data and the length. That means, the reallocation doesn't change mapElem. It changes (*mapElem).ptr.
Apr 09 2016
parent reply Uranuz <neuranuz gmail.com> writes:
On Saturday, 9 April 2016 at 16:44:06 UTC, ag0aep6g wrote:
 On 09.04.2016 18:13, Uranuz wrote:
 http://dpaste.dzfl.pl/523781df67ab
For reference, the code: ---- import std.stdio; void main() { string[][string] mapka; string[]* mapElem = "item" in mapka; //Checking if I have item if( !mapElem ) mapElem = &( mapka["item"] = [] ); //Creating empty element inside map writeln( (*mapElem).capacity ); //Appending should reallocate, so pointer to array should change *mapElem ~= ["dog", "cat", "horse", "penguin", "fish", "frog"]; //But AA still somehow knows the right pointer writeln(mapka); //It works, but I dont understand why? } ---- mapElem is not a pointer to the elements of the array. It's a pointer to the dynamic array structure which holds the pointer to the data and the length. That means, the reallocation doesn't change mapElem. It changes (*mapElem).ptr.
Thanks. It's clear now. AA holds not `array struct` itself inside, but pointer to it. So reallocation affects ptr to allocated memory but not pointer to `array struct`. I think that's it.
Apr 09 2016
parent reply ag0aep6g <anonymous example.com> writes:
On Saturday, 9 April 2016 at 18:06:52 UTC, Uranuz wrote:
 Thanks. It's clear now. AA holds not `array struct` itself 
 inside, but pointer to it.
How the array is stored in the AA doesn't matter, as far as I can see. The point is that you obtain a pointer to the array struct in the AA, not a copy. If you had tried it like the following, mapElem would be a copy of the array struct in the AA, and the append would not affect mapka["item"]: ---- string[] mapElem = "item" in mapka ? mapka["item"] : (mapka["item"] = []); mapElem ~= ["dog", "cat", "horse", "penguin", "fish", "frog"]; ----
 So reallocation affects ptr to allocated memory but not pointer 
 to `array struct`. I think that's it.
Correct.
Apr 09 2016
parent reply Uranuz <neuranuz gmail.com> writes:
On Saturday, 9 April 2016 at 18:27:11 UTC, ag0aep6g wrote:
 On Saturday, 9 April 2016 at 18:06:52 UTC, Uranuz wrote:
 Thanks. It's clear now. AA holds not `array struct` itself 
 inside, but pointer to it.
How the array is stored in the AA doesn't matter, as far as I can see. The point is that you obtain a pointer to the array struct in the AA, not a copy. If you had tried it like the following, mapElem would be a copy of the array struct in the AA, and the append would not affect mapka["item"]: ---- string[] mapElem = "item" in mapka ? mapka["item"] : (mapka["item"] = []); mapElem ~= ["dog", "cat", "horse", "penguin", "fish", "frog"]; ----
 So reallocation affects ptr to allocated memory but not 
 pointer to `array struct`. I think that's it.
Correct.
Another observation is illustrated with the foloving code: http://dpaste.dzfl.pl/8d68fd5922b7 Because AA and arrays are not created before they were assigned some value it leads to inconsistency in behavior. And will produce unexpected and hidden bugs that is not good. It's not very good side of D's array and AA. But classes could be also affected by this *feature* (or bug, as you wish). So we must always construct reference semantics types before passing them to functions that will modify it. For classes it's obvious but for AA and dynamic arrays is not. Another solution is to pass reference types by *ref*. So you will not have such bugs in implementation
Apr 09 2016
next sibling parent reply Uranuz <neuranuz gmail.com> writes:
On Saturday, 9 April 2016 at 19:25:32 UTC, Uranuz wrote:
 On Saturday, 9 April 2016 at 18:27:11 UTC, ag0aep6g wrote:
 [...]
Another observation is illustrated with the foloving code: http://dpaste.dzfl.pl/8d68fd5922b7 Because AA and arrays are not created before they were assigned some value it leads to inconsistency in behavior. And will produce unexpected and hidden bugs that is not good. It's not very good side of D's array and AA. But classes could be also affected by this *feature* (or bug, as you wish). So we must always construct reference semantics types before passing them to functions that will modify it. For classes it's obvious but for AA and dynamic arrays is not. Another solution is to pass reference types by *ref*. So you will not have such bugs in implementation
I think that we need to add warning about such case in documentation section: https://dlang.org/spec/hash-map.html#construction_and_ref_semantic in order to prevent this kind of mistakes in code.
Apr 09 2016
parent reply ag0aep6g <anonymous example.com> writes:
On Saturday, 9 April 2016 at 19:31:31 UTC, Uranuz wrote:
 I think that we need to add warning about such case in 
 documentation section:
 https://dlang.org/spec/hash-map.html#construction_and_ref_semantic

 in order to prevent this kind of mistakes in code.
Isn't that exactly what the section you linked does?
Apr 09 2016
parent Uranuz <neuranuz gmail.com> writes:
On Saturday, 9 April 2016 at 21:16:08 UTC, ag0aep6g wrote:
 On Saturday, 9 April 2016 at 19:31:31 UTC, Uranuz wrote:
 I think that we need to add warning about such case in 
 documentation section:
 https://dlang.org/spec/hash-map.html#construction_and_ref_semantic

 in order to prevent this kind of mistakes in code.
Isn't that exactly what the section you linked does?
Yes certainly. But maybe we should give just a little more example there.
Apr 10 2016
prev sibling parent ag0aep6g <anonymous example.com> writes:
On Saturday, 9 April 2016 at 19:25:32 UTC, Uranuz wrote:
 Another observation is illustrated with the foloving code:
 http://dpaste.dzfl.pl/8d68fd5922b7

 Because AA and arrays are not created before they were assigned 
 some value it leads to inconsistency in behavior. And will 
 produce unexpected and hidden bugs that is not good. It's not 
 very good side of D's array and AA. But classes could be also 
 affected by this *feature* (or bug, as you wish). So we must 
 always construct reference semantics types before passing them 
 to functions that will modify it. For classes it's obvious but 
 for AA and dynamic arrays is not.
The inconsistency is only there with associative arrays, where adding a key may or may not affect other views of the AA, depending on if the AA was empty before or not. In code: ---- int[int] aa1; ... int[int] aa2 = aa1; aa2[0] = 0; /* may or may not affect aa1 */ ---- With dynamic arrays, appending does never affect other views, no matter if the array was empty or not. In code: ---- int[] a1; ... int[] a2 = a1; a2 ~= 1; /* never affects a1 */ ---- Dynamic arrays do have a similar oddity, but it doesn't depend on empty/non-empty. It depends on the capacity. When you append to a dynamic array that has no capacity left, a new copy of the data is made. The dynamic array then becomes independent of other ones with which it used to share data. In code: ---- int[] a1 = [0]; ... int[] a2 = a1; a2 ~= 1; /* never affects a1 */ a2[0] = 1; /* may or may not affect a1 */ ---- With class references I can't see any inconsistency. When you're given a null reference, all you can do is construct an object there, which never affects other copies of the reference. And when you mutate through a non-null reference, that always affects other copies of the references, of course. In code: ---- class C {int x = 0;} C c1; ... C c2 = c1; c2 = new C; /* never affects c1 */ c1 = c2; c2.x = 1; /* obviously affects c1 */ ----
 Another solution is to pass reference types by *ref*. So you 
 will not have such bugs in implementation
Yup, if you mean to affect the passed variable, ref is the way to go. And if you don't mean to affect the passed variable, you should probably mark the parameters const, or at least the reference part of the parameters (e.g. const(int)[]).
Apr 09 2016