Thread overview
Why am I getting segfaults when doing `foreach` with arrays of references?
March 09

With my game project, I have been getting segmentation faults that are unexplainable at my knowledge level. They seem to happen when doing a "foreach" loop through an array of references.

Skip to the bolded text if you don't want to read too much, as I found a way around the first example.

Prior to the latest commit, I had this "foreach" loop lines starting at oe-raylib/source/mission.d:214 (see file). It would segfault on the second line shown here:

        foreach (startTile; this.startingPoints) {
            startTile.occupant.map = this;
            writeln("Just assigned Unit.map to the Mission object.");
            this.factionUnits["player"] ~= *startTile.occupant;
        }

This was in the Mission class (a derivative of Map), which this would be a reference to. startTile is an instance of the GridTile struct, which contains a reference to a Tile object called tile. The Tile object had a pointer to a Unit object called occupant. GridTile.occupant is a convenience function that's equivalent to GridTile.tile.occupant. Finally, the Unit class contains a reference to a Map object called map. Because Mission is a derived class, a Mission object can also fit. All of the things being accessed were public.

I thought the problem was that it was looking in Tile objects with occupant being null, but changing line 214 to the following didn't solve it.

        foreach (startTile; this.startingPoints) if (startTile.occupant !is null) {

Current example
I ended up getting rid of that line causing the segmentation fault and achieving the desired functionality a different way, but now I get a similar one later in the program.

The turnReset function in my Map class does a 'foreach' loop through an array of Unit objects:

    void turnReset() {
        foreach(unit; this.allUnits) {
            unit.turnReset;
        }
    }

My program successfully completed one cycle of the loop, but segfaulted with the second one. I found in GDB that for the second object it didn't even reach the first line in Unit.turnReset(). This may imply that the array length of Map.allUnits is more than one, but no object has been assigned to allUnits[1]. However, there are no lines anywhere in my code that changes the length of this array without appending. I tried doing the grep -r allUnits command to look for every mention of this array in my code, and here is what I have:

source/map.d:    public Unit[] allUnits;
source/map.d:        foreach (unit; this.allUnits) {
source/map.d:        foreach(unit; this.allUnits) {
source/map.d:    assert (canFind(map.allUnits, unit));
source/unit.d:        if (this in map.allUnits) {
oe-raylib/source/mission.d:            this.allUnits ~= *startTile.occupant;
oe-raylib/source/mission.d:        foreach (unit; this.allUnits) {
oe-raylib/source/mission.d:        if (addToMap) allUnits ~= newUnit;
oe-raylib/source/mission.d:    foreach (unit; mission.allUnits) {
oe-raylib/source/mission.d.bak2:        foreach (unit; this.allUnits) {

As far as I see, there's no way that the allUnits array would be longer than the number of objects that have been added to it, so why would calling a public function of one of it's members cause a segfault?

March 09
Something that I have noticed that you are still doing that was pointed out previously is having a pointer to a class reference.

Stuff like ``Tile* currentTile;`` when it should be ``Tile currentTile;``

A class reference is inherently a pointer.

So when you checked for nullability in the foreach loop of mission:

```d
if (startTile.occupant !is null && (*startTile.occupant) !is null) {
```

is what it should've looked like.

```d
import std.stdio : writeln;

void main() {
    Object o = new Object;
    writeln(cast(void*)o); // 7F2780EFD000
}
```
March 09
On Saturday, 9 March 2024 at 06:37:02 UTC, Richard (Rikki) Andrew Cattermole wrote:
> Something that I have noticed that you are still doing that was pointed out previously is having a pointer to a class reference.
>
> Stuff like ``Tile* currentTile;`` when it should be ``Tile currentTile;``
>
> A class reference is inherently a pointer.
>
> So when you checked for nullability in the foreach loop of mission:
>
> ```d
> if (startTile.occupant !is null && (*startTile.occupant) !is null) {
> ```

I think I'm starting to understand better. I was thrown off somewhere when I read that "references in D cannot be null". I tried doing some if-statements to determine if array objects are null, and some of them were.

But that begs the question; why? Don't dynamic arrays always start with a length of 0? If the array was only extended when valid objects were appended using the append operator `~=`, and none of those objects were deleted (as I the destructor was never called), why would some of the array elements be null?
March 09
On 09/03/2024 8:49 PM, Liam McGillivray wrote:
> But that begs the question; why? Don't dynamic arrays always start with a length of 0? If the array was only extended when valid objects were appended using the append operator |~=|, and none of those objects were deleted (as I the destructor was never called), why would some of the array elements be null?

The default initialization state of a pointer (regardless of type) in D is null.

I.e.

```d
Object[] array;
array.length = 2;
array[0] = new Object;

assert(array[0] !is null);
assert(array[1] is null);

array ~= new Object;

assert(array[0] !is null);
assert(array[1] is null);
assert(array[2] !is null);
```
March 09
On Saturday, 9 March 2024 at 07:49:52 UTC, Liam McGillivray wrote:
> But that begs the question; why? Don't dynamic arrays always start with a length of 0? If the array was only extended when valid objects were appended using the append operator `~=`, and none of those objects were deleted (as I the destructor was never called), why would some of the array elements be null?

I'll answer my own question; because the thing assigned to the array was already null.

Anyway, I managed to fix the segfaults. In the latest two commits, I have turned some pointers into references. Now that I understand this, I should have fewer segmentation faults.