Thread overview
Changing behavior of associative array
Dec 16
kdevel
Dec 16
Dennis
December 16

Perhaps someone can help solve this mystery. I have a sample program that adds structs to an associative array (as keys) in a subroutine. The program passes the array by reference as expected (below).

My real program does the same thing and yet the associative array is passed by value. Thus anything added in the subroutine is lost.

I've added a TON of writeln's to confirm this. The instant I made the parameter "ref", the real program starts acting as expected, and the print-outs show the correct results.

Does anyone know why the behavior would change? Has anyone seen anything like this?

I even tried importing the same libraries into both programs. The only remaining difference is that the real function has several more arguments (7), but I would be shocked if D changed its behavior because of that.

thx

import std.stdio;

struct Foo
{
    uint a, b, c;
}

void add_one_and_recurse(
    uint[Foo] m,
    int x)
{
    if (!x)
        return;
    foreach (k,v; m)         // Prints the 1 from main()
        writeln(k, ", ", v); // as expected.
    add_one_and_recurse(m, x-1);
    Foo f = {4, 5, x};
    m[f] = 7;
}

int main()
{
    uint[Foo] m;
    Foo f = {1, 2, 3};
    m[f] = 1;
    add_one_and_recurse(m, 3);
    foreach (k,v; m)         // Prints 4 items
        writeln(k, ", ", v); // as expected.
    return 0;
}
December 16

On Saturday, 16 December 2023 at 20:04:54 UTC, Kevin Bailey wrote:

>

I've added a TON of writeln's to confirm this. The instant I made the parameter "ref", the real program starts acting as expected, and the print-outs show the correct results.

If you comment out this line

//    m[f] = 1;

in your main function of your posted code you can catch up with your
real programm insofar as you now need a ref parameter here, too.

>

Does anyone know why the behavior would change? Has anyone seen anything like this?

Sure.

December 16

On Saturday, 16 December 2023 at 21:30:55 UTC, kdevel wrote:

>

If you comment out this line

//    m[f] = 1;

in your main function of your posted code you can catch up with your
real programm insofar as you now need a ref parameter here, too.

That's because m[f] = 1 initializes the associative array to something non-null. If you pass a null AA to a function which adds things, the caller will still have a null pointers. You can initialize a non-null empty AA like this:

uint[Foo] m = new uint[Foo];

Then, m can be passed by value and you can make additions or removals which the caller sees, unless you assign a new AA in the function.

December 16

On Saturday, 16 December 2023 at 22:44:16 UTC, Dennis wrote:

>

That's because m[f] = 1 initializes the associative array to something non-null. If you pass a null AA to a function which adds things, the caller will still have a null pointers.

I've gotten this error in deployed Perl. Whenever the ceremony of creating a data structure is reduced, people can lose sight of when that happens.

Here's a less common gotcha:

void main() {
    import std.stdio : writeln;

    int force_realloc(ref int[] seq) {
        foreach (int i; 1 .. 1_000_000) {
            seq ~= i;
        }
        return 1234;
    }

    int[] a = [4321];
    writeln(a[0]);  // 4321, of course
    a[0] = force_realloc(a);
    writeln(a[0]);  // still 4321
}

The a[0] on the left of the assignment is decided early, and then force_realloc() changes what the location should be.

December 17

On Saturday, 16 December 2023 at 22:44:16 UTC, Dennis wrote:

>

That's because m[f] = 1 initializes the associative array to something non-null. If you pass a null AA to a function which adds things, the caller will still have a null pointers. You can initialize a non-null empty AA like this:

uint[Foo] m = new uint[Foo];

Then, m can be passed by value and you can make additions or removals which the caller sees, unless you assign a new AA in the function.

Thanks Dennis. I'll have to tweak my mental model of what's happening. I was picturing a std::map-like thing that was passed by reference, but instead it seems like 'm' is a pointer to a std::map, that is initialized on use if null. (I think it's that latter part that gives the illusion of being already initialized.)

December 17

On Sunday, 17 December 2023 at 00:10:56 UTC, Kevin Bailey wrote:

>

instead it seems like 'm' is a pointer to a std::map, that is initialized on use if null. (I think it's that latter part that gives the illusion of being already initialized.)

Yes: https://dlang.org/spec/hash-map.html#construction_and_ref_semantic