Thread overview
Modifying an associative array argument not passed by ref
March 08

If I want to have a function modify an associative array argument, when must I pass it by ref and when is it okay not to?

Concrete example;

import std;

void modifyByRef(AA : V[K], V, K)(ref AA aa)
{
    aa[K.init] = V.init;
}

void modifyNoRef(AA : V[K], V, K)(/*ref*/ AA aa)
{
    aa[K.init] = V.init;
}

auto uniqueKey(AA : V[K], V, K)
    (/*ref*/ AA aa,
    K min = 1,
    K max = K.max,
    V value = V.init)
{
    auto id = uniform(min, max);
    while (id in aa) id = uniform(min, max);

    aa[id] = value;
    return id;
}

void main()
{
    int[int] aa;
    assert(0 !in aa);
    modifyByRef(aa);
    assert(0 in aa);

    int[int] aa2;
    assert(0 !in aa2);
    modifyNoRef(aa2);
    assert(0 in aa2);

    int[int] aa3;
    const key = aa3.uniqueKey;
    assert(key in aa3); // <--
}

The key in aa3 assert fails unless I make uniqueKey take its argument by ref. But modifyNoRef manages just fine without it. They work on the same type and they're all templates. The AAs they are passed have not been assigned any prior values.

What is the deciding difference between those two functions?

March 08

On Saturday, 8 March 2025 at 11:46:50 UTC, Anonymouse wrote:

>
int[int] aa2;
assert(0 !in aa2);
modifyNoRef(aa2);
assert(0 in aa2);

int[int] aa3;
const key = aa3.uniqueKey;
assert(key in aa3); // <--

}


The `key in aa3` assert fails unless I make `uniqueKey` take its argument by ref. But `modifyNoRef` manages just fine without it. They work on the same type and they're all templates. The AAs they are passed have not been assigned any prior values.

What is the deciding difference between those two functions?

assert(0 in aa2) actually fails too.

When an AA is null, it must be passed by ref to modify it. Once it is non-null, its keys and values can be modified. But even a non-null AA cannot be reassigned by passing it without ref.

Note that an AA can be constructed using new before passing by value. See:
https://dlang.org/spec/hash-map.html#construction_and_ref_semantic

March 08

On Saturday, 8 March 2025 at 12:02:41 UTC, Nick Treleaven wrote:

>

assert(0 in aa2) actually fails too.

So it does! My bad. It makes more sense now.

>

When an AA is null, it must be passed by ref to modify it. Once it is non-null, its keys and values can be modified. But even a non-null AA cannot be reassigned by passing it without ref.

Note that an AA can be constructed using new before passing by value. See:
https://dlang.org/spec/hash-map.html#construction_and_ref_semantic

I'll try that, thanks.

March 09
On 3/8/25 4:14 AM, Anonymouse wrote:
> On Saturday, 8 March 2025 at 12:02:41 UTC, Nick Treleaven wrote:
>> `assert(0 in aa2)` actually fails too.
>
> So it does! My bad. It makes more sense now.
>
>> When an AA is null, it must be passed by ref to modify it. Once it is
>> non-null, its keys and values can be modified. But even a non-null AA
>> cannot be reassigned by passing it without ref.
>>
>> Note that an AA can be constructed using `new` before passing by
>> value. See:
>> https://dlang.org/spec/hash-map.html#construction_and_ref_semantic
>
> I'll try that, thanks.

To complete the story, it is the same with arrays:

void main() {
    int[] arr;
    addElement(arr);

    // This array is not affected.
    assert(arr is null);    // Passes
}

void addElement(int[] arr) {
    // Only this local array is initialized.
    arr ~= 42;
}

Ali