Thread overview
Allocating an empty non null associative arary
Mar 31, 2020
Superstar64
Mar 31, 2020
Mike Parker
Mar 31, 2020
Vladimir Panteleev
Mar 31, 2020
WebFreak001
Mar 31, 2020
Superstar64
March 31, 2020
I want to be modify an associative array by reference from another function. However null associative arrays are pass by value. How do I generically create an empty associative array?
---
import std.stdio;


void addElement(int[int] data){
	data[0] = 0;
}

void nonempty() {
	int[int] items = [1 : 1];
	writeln(items); // [1 : 1]
	addElement(items);
	writeln(items); // [0 : 0, 1 : 1]
}

void empty(){
	int[int] items = null;
	writeln(items); // []
	addElement(items);
	writeln(items); // []
}

void main(){
	nonempty();
	empty();
}
---
March 31, 2020
On Tuesday, 31 March 2020 at 02:51:11 UTC, Superstar64 wrote:
> I want to be modify an associative array by reference from another function. However null associative arrays are pass by value. How do I generically create an empty associative array?
> ---
> import std.stdio;
>
>
> void addElement(int[int] data){
> 	data[0] = 0;
> }

void addElement(ref int[int] data){
	data[0] = 0;
}


March 31, 2020
On Tuesday, 31 March 2020 at 02:51:11 UTC, Superstar64 wrote:
> How do I generically create an empty associative array?

If you can't pass it by ref, then adding and then removing an element is the only way I know.

/// Ensure that arr is non-null if empty.
V[K] nonNull(K, V)(V[K] aa)
{
	if (aa !is null)
		return aa;
	aa[K.init] = V.init;
	aa.remove(K.init);
	assert(aa !is null);
	return aa;
}

unittest
{
	int[int] aa;
	assert(aa is null);
	aa = aa.nonNull;
	assert(aa !is null);
	assert(aa.length == 0);
}

March 31, 2020
On Tuesday, 31 March 2020 at 02:51:11 UTC, Superstar64 wrote:
> I want to be modify an associative array by reference from another function. However null associative arrays are pass by value. How do I generically create an empty associative array?
> ---
> import std.stdio;
>
>
> void addElement(int[int] data){
> 	data[0] = 0;
> }
>
> void nonempty() {
> 	int[int] items = [1 : 1];
> 	writeln(items); // [1 : 1]
> 	addElement(items);
> 	writeln(items); // [0 : 0, 1 : 1]
> }
>
> void empty(){
> 	int[int] items = null;
> 	writeln(items); // []
> 	addElement(items);
> 	writeln(items); // []
> }
>
> void main(){
> 	nonempty();
> 	empty();
> }
> ---

This doesn't only happen with null, you will notice that that function will eventually not add anything if you only add values. This is because by adding values you might reallocate the map at some other place in memory and because it's not ref, the caller won't have the map point to the new reference and still only have the old data.

Setting an existing key shouldn't reallocate, though it's not exactly specified but it hints towards it: https://dlang.org/spec/hash-map.html#construction_assignment_entries

So basically if you plan to add or remove items from your map, it could reallocate your memory, so you need to pass your argument by ref, so it updates the caller reference.

// use when inserting or updating
void addElement(scope ref int[int] data) {
	data[0] = 1;
}

// use only when updating existing keys (though you might also consider using ref anyway)
void changeElement(scope int[int] data) {
	data[0] = 2;
}

// use for only reading
int readElement(scope const int[int] data) {
	return data[0];
}

The ref signals the caller that the passed in data will be modified. It's like passing pointers, but the value cannot be null.

The scope signals the caller that you don't escape the reference outside the function, so you can be sure the memory won't be altered after the function has finished. This is especially enforced if you pass -dip1000.

The const signals the caller that you won't modify the data. If you use const, you need to be very strict with it and use it in all your functions consistently, also adding const (or preferably inout) to member functions of structs and classes not modifying their data. (like getters) - You might otherwise need to do ugly casts to remove the const to hack around a function definition which might break the correctness of your program. But if you can do const for your parameters, definitely add it.

The attributes are both for good documentation but also to enforce correct usage by the compiler.
March 31, 2020
On Tuesday, 31 March 2020 at 06:51:16 UTC, WebFreak001 wrote:
> This doesn't only happen with null, you will notice that that function will eventually not add anything if you only add values. This is because by adding values you might reallocate the map at some other place in memory and because it's not ref, the caller won't have the map point to the new reference and still only have the old data.

Is the D spec wrong then? It says that associative arrays have reference semantics. https://dlang.org/spec/hash-map.html#construction_and_ref_semantic

March 31, 2020
On 3/31/20 2:51 AM, WebFreak001 wrote:
> On Tuesday, 31 March 2020 at 02:51:11 UTC, Superstar64 wrote:
>> I want to be modify an associative array by reference from another function. However null associative arrays are pass by value. How do I generically create an empty associative array?
>> ---
>> import std.stdio;
>>
>>
>> void addElement(int[int] data){
>>     data[0] = 0;
>> }
>>
>> void nonempty() {
>>     int[int] items = [1 : 1];
>>     writeln(items); // [1 : 1]
>>     addElement(items);
>>     writeln(items); // [0 : 0, 1 : 1]
>> }
>>
>> void empty(){
>>     int[int] items = null;
>>     writeln(items); // []
>>     addElement(items);
>>     writeln(items); // []
>> }
>>
>> void main(){
>>     nonempty();
>>     empty();
>> }
>> ---
> 
> This doesn't only happen with null, you will notice that that function will eventually not add anything if you only add values. This is because by adding values you might reallocate the map at some other place in memory and because it's not ref, the caller won't have the map point to the new reference and still only have the old data.

This is an incorrect assumption. Associative arrays are pImpls. Once the main bucket holder is allocated it will not change. The bucket array may change, but the elements themselves are each individual allocations. So when the bucket array is grown, it might move, but the elements themselves are stationary, and the bucket holder (the main implementation struct) is stationary.

My recommendation to the OP is to follow Vladimir's advice. I do the same thing in my code.

I have considered adding a UFCS method to AAs to initialize them with zero elements, but haven't got around to it.

-Steve
March 31, 2020
On 3/31/20 9:41 AM, Superstar64 wrote:
> On Tuesday, 31 March 2020 at 06:51:16 UTC, WebFreak001 wrote:
>> This doesn't only happen with null, you will notice that that function will eventually not add anything if you only add values. This is because by adding values you might reallocate the map at some other place in memory and because it's not ref, the caller won't have the map point to the new reference and still only have the old data.
> 
> Is the D spec wrong then? It says that associative arrays have reference semantics. https://dlang.org/spec/hash-map.html#construction_and_ref_semantic
> 

It does have reference semantics, but just like any reference type (pointer, class reference, etc), if it is initialized somewhere else, that initialization does not come back to the original unless you use ref semantics.

Easy to see with a pointer:

void foo(int *ptr)
{
   ptr = new int(5);
}

void main()
{
   int *p;
   foo(p); // does not affect p
}

-Steve