Programming in D – Tutorial and Reference
Ali Çehreli

Other D Resources

Lvalues and Rvalues

The value of every expression is classified as either an lvalue or an rvalue. A simple way of differentiating the two is thinking of lvalues as actual variables (including elements of arrays and associative arrays), and rvalues as temporary results of expressions (including literals).

As a demonstration, the first writeln() expression below uses only lvalues and the other one uses only rvalues:

import std.stdio;

void main() {
    int i;
    immutable(int) imm;
    auto arr = [ 1 ];
    auto aa = [ 10 : "ten" ];

    /* All of the following arguments are lvalues. */

    writeln(i,          // mutable variable
            imm,        // immutable variable
            arr,        // array
            arr[0],     // array element
            aa[10]);    // associative array element
                        // etc.

    enum message = "hello";

    /* All of the following arguments are rvalues. */

    writeln(42,               // a literal
            message,          // a manifest constant
            i + 1,            // a temporary value
            calculate(i));    // return value of a function
                              // etc.
}

int calculate(int i) {
    return i * 2;
}
Limitations of rvalues

Compared to lvalues, rvalues have the following three limitations.

Rvalues don't have memory addresses

An lvalue has a memory location to which we can refer, while an rvalue does not.

For example, it is not possible to take the address of the rvalue expression a + b in the following program:

import std.stdio;

void main() {
    int a;
    int b;

    readf(" %s", &a);          // ← compiles
    readf(" %s", &(a + b));    // ← compilation ERROR
}
Error: a + b is not an lvalue
Rvalues cannot be assigned new values

If mutable, an lvalue can be assigned a new value, while an rvalue cannot be:

    a = 1;          // ← compiles
    (a + b) = 2;    // ← compilation ERROR
Error: a + b is not an lvalue
Rvalues cannot be passed to functions by reference

An lvalue can be passed to a function that takes a parameter by reference, while an rvalue cannot be:

void incrementByTen(ref int value) {
    value += 10;
}

// ...

    incrementByTen(a);        // ← compiles
    incrementByTen(a + b);    // ← compilation ERROR
Error: function deneme.incrementByTen (ref int value)
is not callable using argument types (int)

The main reason for this limitation is the fact that a function taking a ref parameter can hold on to that reference for later use, at a time when the rvalue would not be available.

Different from languages like C++, in D an rvalue cannot be passed to a function even if that function does not modify the argument:

void print(ref const(int) value) {
    writeln(value);
}

// ...

    print(a);        // ← compiles
    print(a + b);    // ← compilation ERROR
Error: function deneme.print (ref const(int) value)
is not callable using argument types (int)
Using auto ref parameters to accept both lvalues and rvalues

As it was mentioned in the previous chapter, auto ref parameters of function templates can take both lvalues and rvalues.

When the argument is an lvalue, auto ref means by reference. On the other hand, since rvalues cannot be passed to functions by reference, when the argument is an rvalue, it means by copy. For the compiler to generate code differently for these two distinct cases, the function must be a template.

We will see templates in a later chapter. For now, please accept that the highlighted empty parentheses below make the following definition a function template.

void incrementByTen()(auto ref int value) {
    /* WARNING: The parameter may be a copy if the argument is
     * an rvalue. This means that the following modification
     * may not be observable by the caller. */

    value += 10;
}

void main() {
    int a;
    int b;

    incrementByTen(a);        // ← lvalue; passed by reference
    incrementByTen(a + b);    // ← rvalue; copied
}

It is possible to determine whether the parameter is an lvalue or an rvalue by using __traits(isRef) with static if :

void incrementByTen()(auto ref int value) {
    static if (__traits(isRef, value)) {
        // 'value' is passed by reference
    } else {
        // 'value' is copied
    }
}

We will see static if and __traits later in the Conditional Compilation chapter.

Terminology

The names "lvalue" and "rvalue" do not represent the characteristics of these two kinds of values accurately. The initial letters l and r come from left and right, referring to the left- and the right-hand side expressions of the assignment operator:

The terms "left value" and "right value" are confusing because in general both lvalues and rvalues can be on either side of an assignment operation:

    // rvalue 'a + b' on the left, lvalue 'a' on the right:
    array[a + b] = a;