www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Confusing behaviour of array member assignment when calling local

reply Sahan Fernando <sahan.h.fernando.alt gmail.com> writes:
Hello all,

I've encountered an unexpected behaviour in the D language, where 
assigning the result of a function that grows an array to a value 
in that array ends up modifying the old memory used previously by 
that array. This seems counterintuitive to me in the sense that 
it feels logical that the lvalue to be assigned to should be 
evaluated after the function has been evaluated, but this 
behaviour implies that part of the lvalue was evaluated before 
the function.

A minimal reproduction of the issue I'm describing is attached 
below:

```
import std.exception;

void main() {
     auto v = new ulong[1];
     size_t appendAndGetLength() {
         v ~= [10, 10, 10, 10, 10, 10, 10, 10, 10, 10];
         return v.length;
     }
     v[0] = appendAndGetLength();
     // Works if we do the following instead
     // auto v0 = appendAndGetLength();
     // v[0] = v0;
     enforce(v[0] == v.length);
}
```

When running this code, it fails the enforcement because v[0] 
evaluates to 0 at the end, this ceases to be a problem if I 
introduce a temporary variable. I am confused as to why this is 
the case, and why adding the temporary fixed it. Can someone 
please help me determine why this is happening? It seems 
counterintuitive, and almost feels a bit like a footgun.

I tried looking in the spec for references to why this was 
happening, the closest I could find was a [footnote about 
undefined behaviour if aliased reference types are 
assigned](https://dlang.org/spec/expression.html#assign_expressions). I believe
that this shouldn't apply in this case, because while the lvalue of the
assignment is a reference to the array, the rvalue is a size_t (the result of
evaluating the function).
Jan 04
next sibling parent monkyyy <crazymonkyyy gmail.com> writes:
On Saturday, 4 January 2025 at 23:11:58 UTC, Sahan Fernando wrote:
 almost feels a bit like a footgun.
because it is [] is both a dynamic array and a reference; dont do both at once It a #wontfix
Jan 04
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Saturday, 4 January 2025 at 23:11:58 UTC, Sahan Fernando wrote:
 Hello all,

 I've encountered an unexpected behaviour in the D language, 
 where assigning the result of a function that grows an array to 
 a value in that array ends up modifying the old memory used 
 previously by that array. This seems counterintuitive to me in 
 the sense that it feels logical that the lvalue to be assigned 
 to should be evaluated after the function has been evaluated, 
 but this behaviour implies that part of the lvalue was 
 evaluated before the function.

 A minimal reproduction of the issue I'm describing is attached 
 below:

 ```d
 import std.exception;

 void main() {
     auto v = new ulong[1];
     size_t appendAndGetLength() {
         v ~= [10, 10, 10, 10, 10, 10, 10, 10, 10, 10];
         return v.length;
     }
     v[0] = appendAndGetLength();
     // Works if we do the following instead
     // auto v0 = appendAndGetLength();
     // v[0] = v0;
     enforce(v[0] == v.length);
 }
 ```
So what is happening here is the left side of the assignment is being evaluated first. Then the right side. The right side alters `v` by appending (it must reallocate to accomodate the new values), and therefore you assign to the *old* `v`. This can be demonstrated: ```d void main() { auto v = new ulong[1]; auto oldv = v; size_t appendAndGetLength() { v ~= [10, 10, 10, 10, 10, 10, 10, 10, 10, 10]; return v.length; } v[0] = appendAndGetLength(); enforce(oldv.ptr !is v.ptr); enforce(oldv[0] == v.length); } ``` A dynamic array (or slice) is simply a pointer and a length. It is *not* a reference into an array object (many other languages are like this, D is not). You can read more about how arrays work here: https://dlang.org/articles/d-array-article.html -Steve
Jan 04
parent Sahan Fernando <sahan.h.fernando.alt gmail.com> writes:
On Sunday, 5 January 2025 at 02:33:51 UTC, Steven Schveighoffer 
wrote:
 So what is happening here is the left side of the assignment is 
 being evaluated first. Then the right side. The right side 
 alters `v` by appending (it must reallocate to accomodate the 
 new values), and therefore you assign to the *old* `v`.

 ...

 You can read more about how arrays work here: 
 https://dlang.org/articles/d-array-article.html

 -Steve
Ok, that article helped clear up some of the misunderstanding, I guess it makes sense why it works the way it does. It still feels a bit counterintuitive, but I guess I should just treat it as another thing to avoid doing by accident (similar to modifying a collection while iterating over it). Thanks for the reply!
Jan 05
prev sibling parent Nick Treleaven <nick geany.org> writes:
On Saturday, 4 January 2025 at 23:11:58 UTC, Sahan Fernando wrote:
 Hello all,

 I've encountered an unexpected behaviour in the D language, 
 where assigning the result of a function that grows an array to 
 a value in that array ends up modifying the old memory used 
 previously by that array. This seems counterintuitive to me in 
 the sense that it feels logical that the lvalue to be assigned 
 to should be evaluated after the function has been evaluated, 
 but this behaviour implies that part of the lvalue was 
 evaluated before the function.
https://dlang.org/spec/expression.html#order-binary says:
 Implementation Defined: The order of evaluation of the operands 
 of AssignExpression.
Jan 06