Lifetimes

We discuss the lifetimes of variables and parameters in this lesson.

We'll cover the following

Chapter overview

We will soon cover structs, the basic feature that allows programmers to define application-specific types. Structs are for combining fundamental types and other structs together to define higher-level types that behave according to the special needs of programs. After structs, you will learn about classes, which are the basis of the object-oriented programming features of D.

Before jumping to structs and classes, we will talk about some important concepts first. These concepts will help you understand structs and classes and some of their differences.

We will call any piece of data that represents a concept in a program a variable. In a few places, we will refer to struct and class variables specifically as objects. We will continue calling both of these concepts variables in this chapter.

Although this chapter includes only the fundamental types, slices, and associative arrays, these concepts apply to user-defined types as well.

Lifetime of a variable

The time between when a variable is defined and when it is finalized is the lifetime of that variable. Although it is the case for many types, becoming unavailable and being finalized need not occur at the same time.

Let’s recall how variables become unavailable. In simple cases, exiting the scope where a variable was defined would render that variable unavailable.

Let’s consider the following example as a reminder:

svg viewer
void speedTest() {
int speed; // Single variable ...
foreach (i; 0 .. 10) {
speed = 100 + i; // ... takes 10 different values.
}
write(speed);
} // ← 'speed' is unavailable beyond this point.

The lifetime of the speed variable in that code ends upon exiting the speedTest() function. There is a single variable in the code above, which takes ten different values from 100 to 109.

When it comes to variable lifetimes, the following code is very different compared to the previous one:

void speedTest() {
foreach (i; 0 .. 10) {
int speed = 100 + i; // Ten separate variables.
// ...
} // ← Lifetime of each variable ends here.
write(speed);
}

There are ten separate variables in that code, each taking a single value. Upon every iteration of the loop, a new variable starts its life, which eventually ends at the end of each iteration.

Lifetime of a parameter

The lifetime of a parameter depends on its qualifiers:

  • ref: This parameter is just an alias of the actual variable that is specified when calling the function. ref parameters do not affect the lifetimes of actual variables.

  • in: For value types, the lifetime of the in parameter starts upon entering the function and ends upon exiting it. For reference types, the lifetime of the parameter is the same as with ref.

  • out: Same with ref, this parameter is just an alias of the actual variable that is specified when calling the function. The only difference is that the variable is set to its .init value automatically upon entering the function.

  • lazy: The life of the lazy parameter starts when the parameter is actually used and ends right then.

The following example uses these four types of parameters and explains their lifetimes in program comments:

void main() {
int main_in; /* The value of main_in is copied to the
* parameter. */
int main_ref; /* main_ref is passed to the function as
* itself. */
int main_out; /* main_out is passed to the function as
* itself. Its value is set to int.init
* upon entering the function. */
foo(main_in, main_ref, main_out, aCalculation());
}
void foo(
in int p_in, /* The lifetime of p_in starts upon
* entering the function and ends upon
* exiting the function. */
ref int p_ref, /* p_ref is an alias of main_ref. */
out int p_out, /* p_out is an alias of main_out. Its
* value is set to int.init upon
* entering the function. */
lazy int p_lazy) { /* The lifetime of p_lazy starts when
* it is used and ends when its use
* ends. Its value is calculated by
* calling aCalculation() every time
* p_lazy is used in the function. */
// ...
}
int aCalculation() {
int result;
// ...
return result;
}