Search⌘ K
AI Features

The Concept of a Reference

Explore the concept of references and pointers in D programming. Understand how ref variables in loops and functions modify data directly. Learn about reference types like classes, slices, and associative arrays, and their implementation through pointers. This lesson helps you grasp essential low-level memory access techniques in D for more effective system programming.

Pointers

Pointers are variables that provide access to other variables. The value of a pointer is the address of the variable that it provides access to.

Pointers can point at any type of variable, object, and even other pointers. In this chapter, we will refer to all of these simply as variables.

Pointers are low-level features of microprocessors. They are an important part of system programming.

The syntax and semantics of pointers in D are inherited directly from C. Although pointers are notoriously the most difficult feature of C to comprehend, they should not be as difficult in D. This is because other features of D that are semantically close to pointers are more useful in situations where pointers would have to be used in other languages. When the ideas behind pointers are already understood from other features of D, pointers should be easier to grasp.

The names like ptr (short for “pointer”) that we have used in these examples should not be considered as useful names in general. As always, names must be chosen to be more meaningful and explanatory in actual programs.

The concept of a reference

Although we have encountered references many times in the previous chapters, let’s summarize this concept one more time.

The ref variables in foreach loops

As we have seen in the foreach loop chapter, normally the loop variables are copies of elements:

D
import std.stdio;
void main() {
int[] numbers = [ 1, 11, 111 ];
foreach (number; numbers) {
number = 0; // ← the copy changes, not the element
}
writeln("After the loop: ", numbers);
}

The number that gets assigned 0 each time is a copy of one of the elements of the array. Modifying that copy does not modify the element.

When the actual elements need to be modified, the foreach variable must be defined as ref:

D
import std.stdio;
void main() {
int[] numbers = [ 1, 11, 111 ];
foreach (ref number; numbers) {
number = 0; // ← the actual element changes
}
writeln("After the loop: ", numbers);
}

This time, number is a reference to an actual element in the array.

ref function parameters

As you have seen in the function parameters chapter, the parameters of value types are normally copies of the arguments:

D
import std.stdio;
void addHalf(double value) {
value += 0.5; // ← Does not affect 'value' in main
}
void main() {
double value = 1.5;
addHalf(value);
writeln("The value after calling the function: ", value);
}

Because the function parameter is not defined as ref, the assignment inside the function affects only the local variable there. The variable in main() is not affected.

The ref keyword would make the function parameter a reference to the argument:

D
import std.stdio;
void addHalf(ref double value) {
value += 0.5;
}
void main() {
double value = 1.5;
addHalf(value);
writeln("The value after calling the function: ", value);
}

This time, the variable in main() gets modified.

Reference types

Some types are reference types. Variables of such types provide access to separate variables:

  • Class variables
  • Slices
  • Associative arrays

We have discussed this distinction in the value types and reference types chapter. The following example demonstrates reference types using two class variables:

D
import std.stdio;
class Pen {
double ink;
this() {
ink = 15;
}
void use(double amount) {
ink -= amount;
}
}
void main() {
auto pen = new Pen;
auto otherPen = pen; // ← Now both variables provide
// access to the same object
writefln("Before: %s %s", pen.ink, otherPen.ink);
pen.use(1); // ← the same object is used
otherPen.use(2); // ← the same object is used
writefln("After : %s %s", pen.ink, otherPen.ink);
}

Because classes are reference types, the class variables pen and otherPen provide access to the same Pen object. As a result, using either of those class variables affects the same object.

That single object and the two-class variables would be laid out in memory similar to the following figure:

widget

References point at actual variables like pen and otherPen do above.

Programming languages implement the reference and pointer concepts with special registers of the microprocessor, which are specifically for pointing at memory locations.

Behind the scenes, D’s higher-level concepts (class variables, slices, associative arrays, etc.) are all implemented by pointers. As these higher-level features are already efficient and convenient, pointers are rarely needed in D programming. Still, it is important for D programmers to understand pointers well.