Programming in D – Tutorial and Reference
Ali Çehreli

Other D Resources

alias and with

alias

The alias keyword assigns aliases to existing names. alias is different from and unrelated to alias this.

Shortening a long name

As we have encountered in the previous chapter, some names may become too long to be convenient. Let's consider the following function from that chapter:

Stack!(Point!double) randomPoints(size_t count) {
    auto points = new Stack!(Point!double);
    // ...
}

Having to type Stack!(Point!double) explicitly in multiple places in the program has a number of drawbacks:

These drawbacks can be eliminated by giving a new name to Stack!(Point!double):

alias Points = Stack!(Point!double);

// ...

Points randomPoints(size_t count) {
    auto points = new Points;
    // ...
}

It may make sense to go further and define two aliases, one taking advantage of the other:

alias PrecisePoint = Point!double;
alias Points = Stack!PrecisePoint;

The syntax of alias is the following:

    alias new_name = existing_name;

After that definition, the new name and the existing name become synonymous: They mean the same thing in the program.

You may encounter the older syntax of this feature in some programs:

    // Use of old syntax is discouraged:
    alias existing_name new_name;

alias is also useful when shortening names which otherwise need to be spelled out along with their module names. Let's assume that the name Queen appears in two separate modules: chess and palace. When both modules are imported, typing merely Queen would cause a compilation error:

import chess;
import palace;

// ...

    Queen person;             // ← compilation ERROR

The compiler cannot decide which Queen has been meant:

Error: chess.Queen at chess.d(1) conflicts with
palace.Queen at palace.d(1)

A convenient way of resolving this conflict is to assign aliases to one or more of the names:

import palace;

alias PalaceQueen = palace.Queen;

void main() {
    PalaceQueen person;
    // ...
    PalaceQueen anotherPerson;
}

alias works with other names as well. The following code gives a new name to a variable:

    int variableWithALongName = 42;

    alias var = variableWithALongName;
    var = 43;

    assert(variableWithALongName == 43);
Design flexibility

For flexibility, even fundamental types like int can have aliases:

alias CustomerNumber = int;
alias CompanyName = string;
// ...

struct Customer {
    CustomerNumber number;
    CompanyName company;
    // ...
}

If the users of this struct always type CustomerNumber and CompanyName instead of int and string, then the design can be changed in the future to some extent, without affecting user code.

This helps with the readability of code as well. Having the type of a variable as CustomerNumber conveys more information about the meaning of that variable than int.

Sometimes such type aliases are defined inside structs and classes and become parts of the interfaces of those types. The following class has a weight property:

class Box {
private:

    double weight_;

public:

    double weight() const @property {
        return weight_;
    }
    // ...
}

Because the member variable and the property of that class is defined as double, the users would have to use double as well:

    double totalWeight = 0;

    foreach (box; boxes) {
        totalWeight += box.weight;
    }

Let's compare it to another design where the type of weight is defined as an alias:

class Box {
private:

    Weight weight_;

public:

    alias Weight = double;

    Weight weight() const @property {
        return weight_;
    }
    // ...
}

Now the user code would normally use Weight as well:

    Box.Weight totalWeight = 0;

    foreach (box; boxes) {
        totalWeight += box.weight;
    }

With this design, changing the actual type of Weight in the future would not affect user code. (That is, if the new type supports the += operator as well.)

Revealing hidden names of superclasses

When the same name appears both in the superclass and in the subclass, the matching names that are in the superclass are hidden. Even a single name in the subclass is sufficient to hide all of the names of the superclass that match that name:

class Super {
    void foo(int x) {
        // ...
    }
}

class Sub : Super {
    void foo() {
        // ...
    }
}

void main() {
    auto object = new Sub;
    object.foo(42);            // ← compilation ERROR
}

Since the argument is 42, an int value, one might expect that the Super.foo function that takes an int would be called for that use. However, even though their parameter lists are different, Sub.foo hides Super.foo and causes a compilation error. The compiler disregards Super.foo altogether and reports that Sub.foo cannot be called by an int:

Error: function deneme.Sub.foo () is not callable
using argument types (int)

Note that this is not the same as overriding a function of the superclass. For that, the function signatures would be the same and the function would be overridden by the override keyword. (The override keyword has been explained in the Inheritance chapter.)

Here, not overriding, but a language feature called name hiding is in effect. If there were not name hiding, functions that happen to have the same name foo that are added to or removed from these classes might silently change the function that would get called. Name hiding prevents such surprises. It is a feature of other OOP languages as well.

alias can reveal the hidden names when desired:

class Super {
    void foo(int x) {
        // ...
    }
}

class Sub : Super {
    void foo() {
        // ...
    }

    alias foo = Super.foo;
}

The alias above brings the foo names from the superclass into the subclass interface. As a result, the code now compiles and Super.foo gets called.

When it is more appropriate, it is possible to bring the names under a different name as well:

class Super {
    void foo(int x) {
        // ...
    }
}

class Sub : Super {
    void foo() {
        // ...
    }

    alias generalFoo = Super.foo;
}

// ...

void main() {
    auto object = new Sub;
    object.generalFoo(42);
}

Name hiding affects member variables as well. alias can bring those names to the subclass interface as well:

class Super {
    int city;
}

class Sub : Super {
    string city() const @property {
        return "Kayseri";
    }
}

Regardless of one being a member variable and the other a member function, the name city of the subclass hides the name city of the superclass:

void main() {
    auto object = new Sub;
    object.city = 42;        // ← compilation ERROR
}

Similarly, the names of the member variables of the superclass can be brought to the subclass interface by alias, possibly under a different name:

class Super {
    int city;
}

class Sub : Super {
    string city() const @property {
        return "Kayseri";
    }

    alias cityCode = Super.city;
}

void main() {
    auto object = new Sub;
    object.cityCode = 42;
}
with

with is for removing repeated references to an object or symbol. It takes an expression or a symbol in parentheses and uses that expression or symbol when looking up other symbols that are used inside the scope of with:

struct S {
    int i;
    int j;
}

void main() {
    auto s = S();

    with (s) {
        i = 1;    // means s.i
        j = 2;    // means s.j
    }
}

It is possible to create a temporary object inside the parentheses. In that case, the temporary object becomes an lvalue, lifetime of which ends upon leaving the scope:

    with (S()) {
        i = 1;    // the i member of the temporary object
        j = 2;    // the j member of the temporary object
    }

As we will see later in the Pointers chapter, it is possible to construct the temporary object with the new keyword, in which case its lifetime can be extended beyond the scope.

with is especially useful with case sections for removing repeated references to e.g. an enum type:

enum Color { red, orange }

// ...

    final switch (c) with (Color) {

    case red:       // means Color.red
        // ...

    case orange:    // means Color.orange
        // ...
    }
Summary