EGL Development User Group - Group home

References in EGL

  
The mystery of the lost changes
Not long ago, I was asked to help solve a mystery. A developer had stored a record in a dictionary, and he wondered why changes to the record were being lost. His code looked like this:
// Declare the Dictionary.
newData Dictionary{};

// ...Do many things. Put a Record into the Dictionary...

// ...Do more things...

// Get the Record from the Dictionary, then update it.
dataRec DataRecord = newData.key as DataRecord;
dataRec.quantity = dataRec.quantity + loadedData[i].quantity;
Later, he got the record out of the dictionary again, like this:
rec2 DataRecord = newData.key as DataRecord;
When he examined rec2.quantity, he was surprised. It still had its default value, as if the assignment to dataRec.quantity had never happened. Do you know why?

Solving the mystery
This happened because the variable rec is a record, and a record is not a reference type. When you assign one record to another, as in the statement dataRec DataRecord = newData.key as DataRecord;, the data of the fields are copied. This may be obvious to some of you, but it's a foreign concept to developers used to a language like Java where everything is a reference. Here's another way to write the lines which get the record from the dictionary and update it.
// Get the Record from the Dictionary, then update it.
dataRec DataRecord;
dataRec.quantity = (newData.key as DataRecord).quantity;
dataRec.quantity = dataRec.quantity + loadedData[i].quantity;
This is exactly what the original code does, but now it's clear that the record in the dictionary won't change.

Values and references
In EGL, there are two kinds of types: values and references. Value types are for "normal" variables. They store a piece of data and they're not pointers. When you assign one value to another, the data is copied. Most types in EGL are value types, such as int, char, and records.

A reference is a kind of pointer, but unlike pointers in most programming languages, you don't have to use any special syntax to get or set what it's pointing to. When you assign a reference to another reference, only the pointer is copied. That means both references will be pointing to the same value. EGL's reference types include any, dictionary, and arrays. (Note: fields within a structured record that look like arrays are actually occurred items. Unlike arrays, they have a fixed size and they're not references.)

Mixing values and references
Can you ever assign a reference to a value, or a value to a reference? Yes, in one case. EGL's any type is a reference type, and it's compatible with everything, even values. When you assign a value to an any, we make the any point to a copy of the original value. When you assign an any to a value, another copy is made: the value is assigned a copy of whatever the any is pointing to.

Copying values and how to avoid it
Let's return to the example from the start of this blog entry. The type of a dictionary's value is any. So it's not correct to say that the record is stored in the dictionary. Actually, the dictionary stores a copy of the record. Then, when we pull the record out of the dictionary, it's copied again. Two copies! And another copy every time you get the record! If the record is large then this might not perform well.

You can store your data in a reference variable to reduce the amount of copying. One way to do this is to get rid of the record, and store your data directly in the dictionary. In other words, rather than putting data into record.fieldA, record.fieldB, etc., put the data into dictionary.fieldA, dictionary.fieldB, etc. Now you can access each field individually. You can get the value of a particular field without EGL making a copy of the entire record.

Before:
// Create a record with two fields, and a dictionary.
r MyRecord;
r.fieldA = 7;
r.fieldB = "hello";
d dictionary{};

// Put a COPY of the record into the dictionary.
d.key = r;

// Get fieldA of a COPY of the record from the dictionary.
r2 MyRecord = d.key;
writeStdout( "fieldA is " :: r2.fieldA );

// ...later...get fieldB of a COPY of the record from the dictionary.
r3 MyRecord = d.key;
writeStdout( "fieldB is " :: r3.fieldB );
After:
// Create a dictionary with two fields.
d dictionary{};
d.fieldA = 7;
d.fieldB = "hello";

// Get fieldA.
writeStdout( "fieldA is " :: d.fieldA );

// ...later...get fieldB.
writeStdout( "fieldB is " :: d.fieldB );
EGL is still making a copy of fieldA and fieldB when you get them from the dictionary. The difference is that only the field you want is copied.

You can achieve the same thing by using an array of records, and storing the array in the dictionary. Since an array is a reference, it's not copied during assignments. Here's how to do it:
// Create an array of records and a dictionary.
r MyRecord[1];
r[1].fieldA = 7;
r[1].fieldB = "hello";
d dictionary{};

// Put the array into the dictionary.
// The array and its record are not copied.
d.key = r;

// Get fieldA without copying the array or record.
tempRecArray MyRecord[] = d.key;
writeStdout( "fieldA is " :: tempRecArray[1].fieldA );

// ...later...get fieldB without copying the array or record.
tempRecArray2 MyRecord[] = d.key;
writeStdout( "fieldB is " :: tempRecArray2[1].fieldB );

Keep in mind the standard warning for every recommendation about performance: your mileage may vary. These techniques make your code a little more complicated, so before you use them do some analysis to ensure they make a significant difference.

Matt