Upcoming Events
5th Australasian Conference on Interactive Entertainment
12/3 - 12/5 @ Brisbane, Australia

2K Bot Prize
12/15 - 12/18 @ Perth, Australia

IEEE Symposium on Computational Intelligence and Games
12/15 - 12/18 @ Perth, Australia

IEEE Consumer Communications & Networking Conference
1/10 - 1/13 @ Las Vegas, NV

More events...


Quick Stats
4292 people currently visiting GDNet.
2240 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!



Link to us

  search:   

Learn to Tango with D Excerpt: Chapter 2
D Fundamentals


A movie director once made the observation that Charlie Sheen and Emilio Estevez both resemble their father, Martin Sheen, but look nothing like each other. Something similar might be said of programming languages that belong to the C family. They all share certain features of C, but beyond that, they have several differences. For the most part, the differences are to be found in expanded features, such as inherent support for object-oriented or generic programming, automatic memory management, or features intended to make programs more secure and robust. But at the core of each of these languages is a set of features that vary little from one language to the next. D, too, follows this pattern, but does so in a way that makes it stand out from the rest.

As you’ll see in later chapters, some of D’s more advanced features improve on ideas already implemented in other modern languages derived from C; some were inspired by languages outside the family; and a few are not to be found in any other mainstream programming language. While these features all contribute to D’s unique identity, many users are first drawn to the language by the core feature set. In this chapter, we’ll look at these core features.

We’ll start out with declarations before moving on to the basic types. Next, we’ll look at the different kinds of arrays and array operations. Then we’ll get into flow-control constructs. Finally, we’ll discuss functions and error handling.

Declarations

When declaring variables in D, the syntax varies depending on the type of the variable. When we discuss basic types, arrays, and pointers, we’ll look at the syntax for variable declarations of each. Before we get that far though, it is helpful to understand some general rules about declarations in D.

Declaration Syntax and Variable Initialization

D is a statically typed language, which means that the type of a variable must be known at compile time. Therefore, D variable declarations usually require the type to be a part of the declaration. We say “usually,” because there is one exception to this rule, which we’ll get to in a moment. Declarations read from right to left and must be terminated by a semicolon, as in the following examples:

int x;
int y = 1;
char[] myString = "Hello";
float[5] fiveFloats;
long* pointer = null;
This code declares one variable x of type int that is not explicitly initialized, and another variable y of type int that is explicitly initialized to 1. The variable myString is an example of declaring an initialized, dynamic array. All strings in D are arrays of one of three character types. fiveFloats is a static array, which is not explicitly initialized in this example. Finally, the pointer variable is an example of a pointer declaration in D.

Notice that we said that the variables x and fiveFloats are “not explicitly initialized,” rather than “uninitialized.” This is because no D variable is ever left uninitialized at the point of declaration. If you do not explicitly initialize the variable to some value, it will automatically be initialized to a specific default value by the compiler. The value used for initialization depends on the variable’s type, but it is guaranteed to be the same for all variables of the same type. This is a very useful feature for debugging. You’ll see the different default initialization values when we examine each type.


Caution: Automatic variable initialization is intended to catch uninitialized variables, a common source of bugs. However, don’t consider it an opportunity to avoid initializing variables yourself. As you’ll see when we discuss floating-point numbers, it is not a good idea get into the habit of relying on automatic variable initialization to do your job for you.

Another important part of a variable declaration is the name, or identifier, used to represent the variable. When creating any identifier—whether it is the name of a variable, function, class, struct, or whatever—you need to keep a few rules in mind:

  • Identifiers can begin with a letter, an underscore (_), or a universal alpha character.
  • The first character can be followed by any number of letters or universal alpha characters.
  • You can use as many underscores in the identifier as you like, as long as you don’t use them for both the first and second characters. Identifiers beginning with two underscores are reserved for use by the compiler.
  • Identifiers are case-sensitive, so x and X are not the same.

Note: Universal alphas are characters from several different languages. They are defined, using hexadecimal codes, in Appendix D of the C99 standard as being legal for use in C identifiers. Because D is derived from C, it accepts the same characters in identifier names.

An optional part of variable declaration is the storage class. The storage class of a construct determines when it is allocated, where it is stored, how long it lives, how it is accessed, and, in some cases, how the compiler views it. D reserves several keywords for indicating the storage class of different language constructs. In this chapter, we are concerned with only those that affect variables and functions, as using a storage class alters the syntax of a declaration. A storage class commonly used with individual variables is const. This tells the compiler that a given variable is to be treated as a constant expression, meaning that its value should not change during runtime. Another commonly used storage class is extern, which indicates that a variable is initialized outside the current binary. This is frequently used when creating D modules that interact with C libraries. When using a storage class in a variable declaration, it must precede the type:

const int x = 1;
However, in some cases, the type can be omitted:
const y = 1;
Here, the type of y is omitted. This form uses a feature of D called automatic type inference. As long as a declaration contains a storage class, the type can be omitted, and the compiler will infer it automatically. Because a storage class is intended to affect the variable in some way, D provides a special storage class, auto, for those cases where you want to use automatic type inference but don’t want any storage class side effects. In other words, auto does not affect the variable in any way at all and indicates only that type inference is to be used. Using auto together with the type in the declaration is not an error, but has no meaning. Here’s an example of using the auto storage class:
auto x = 1;        // The type will automatically be inferred as int.
auto int y = 1;    // auto has no effect here, since the type is specified.
There’s quite a bit more to say about declarations. We’ll get to the specifics for various constructs as the chapter progresses. First, we need to lay some more groundwork and talk about D’s scoping rules.

Declarations and Scope

The term scope describes the context in which a particular declaration resides. Scope affects variable declarations in two ways:

  • It determines when and how you can initialize your variables.
  • Because scope controls which variables are visible, it also affects how you can name your identifiers.
In this chapter, we are concerned with two basic types of scope: module scope and block scope. When you create a new D source file, you are working in module scope by default. You usually create a new block scope with each matching pair of curly braces you add to the file.
Note: Module scope is also referred to as global scope. Block scope is often called local scope. D also has a special scope that is unique to classes and structs, generally referred to as class scope. You’ll learn about classes and structs in Chapter 3.

The following example shows module scope and global scope.

// This is module scope. Here, we declare x and initialize it with a constant
// expression.
int x = 1;

void main()
{   // A new block scope starts here--a child of the module scope.
    // y is declared inside main's block scope, meaning it is local to main.
    // It can see x, but x can't see it.
    int y = x;

    if(1 < 2)
    {   // A new block scope starts here--a child of main's scope.
    
        // Because x is visible in main's scope, it is also visible here. And
        // because main's scope is this scope's parent, y is visible, too.
        // However, z is visible neither in main's scope nor in the module
        // scope.
        int z = x + y;
        
    }   // The end of the if block scope
        
}   // The end of main's block scope

void someFunc()
{   // A new block scope starts here--a child of the module scope and a 
    // sibling of main's scope.

    // This y is declared inside someFunc's scope. It can see x, but x can't
    // see it. Also, neither it nor the y in main's scope are visible to each
    // other.
    int y = x;

}   // The end of someFunc's block scope

Note: The example of using module and block scope employs some features that we haven’t yet discussed, such as functions. For now, you just need to focus on the meaning of scope demonstrated by the code. You’ll learn about the other features later in this chapter and in upcoming chapters.

In this example, the variable x is declared outside any curly braces. This indicates that it is in module scope. The curly braces in the main function introduce a new block scope. It is in this scope that a variable y resides. Similarly, the function someFunc creates a new block scope with its own y variable.

The code comments in the listing explain scope visibility. Essentially, children can see identifiers that are visible in, or declared in, their parent, but parents can never see identifiers declared in their children. Neither can siblings see each other’s identifiers.

As noted, both main and someFunc declare a variable y. They can do this since neither scope is visible to the other. However, neither main nor someFunc could declare a variable x, since x is already visible in both scopes. Identifier names must be unique within the scope in which they are declared. If you create a new identifier using a name that is already visible in that scope, the compiler will fail with an error.

This example also explicitly initializes the variables it declares. Within a block scope, there aren’t any restrictions on how you explicitly initialize a variable. However, in the module scope, you can initialize only variables with constant expressions. It is illegal to use a nonconstant expression to explicitly initialize a variable at module scope. Doing so will result in a compiler error. The following shows examples of legal and illegal module scope variable initialization.

int x = 1;        // OK: 1 is a constant expression.
int y = x;        // Error: x is not a constant expression.
int z = 1 + 1;    // OK: 1 + 1 is a constant expression.
int a = 1 + x;    // Error: 1 is constant, x is not, so 1 + x is a
                  // nonconstant expression.
Because both y and a depend on x, which is a nonconstant expression, neither can be explicitly initialized at module scope. Instead, they should be assigned their values elsewhere in the program in a block scope, such as a function. Assignments, other than those made at the point of declaration, are illegal at module scope. Again, none of these restrictions apply to block scope.
Tip: One way to “fake” initialization with nonconstant expressions at the module scope is to make the declaration as normal, without explicitly initializing it, and then assign the variable its value inside a static module constructor. A module constructor is a unique D feature that is quite handy for this purpose. You will learn about module constructors in Chapter 4.

Constant and Nonconstant Expressions

The term expression is often used in language specifications. It is bandied about by compiler writers, who tend to use a great deal of vocabulary that average programmers forgot about after their last comp-sci course, or never knew at all. The D Programming Language specification defines the term as follows:

Expressions are used to compute values with a resulting type. These values can then be assigned, tested, or ignored. Expressions can also have side effects.

Any single value, such a 1 or an x, is an expression. An arithmetic operation, such as 1+1 or 1+x, is an expression. A function call that returns a value is an expression. All of these can be used anywhere the language specification says an expression is legal, such as making an assignment to a variable.

A constant expression is one whose value can be known at compile time and is never going to change. For example, 1 is a constant because it is always 1, just as 1+1 always results in the value 2. Because the values of constant expressions never change, the compiler can make certain optimizations with constant expressions that it otherwise wouldn’t be able to do.

One popular feature of D is compile time function execution (CTFE). This feature is built around constant expressions, allowing programmers to create functions and other expressions that are evaluated at compile time rather than at runtime. This allows the D compiler to make extraordinary optimizations that would not be possible otherwise. You’ll learn about CTFE in Chapter 5.

A nonconstant expression is one whose value can change, and usually does. For example, the declaration int x = 1 assigns a constant expression to the variable x, meaning that the initial value of x is known at compile time, but x itself is nonconstant because it can change at runtime. The compiler is unable to make the optimizations for x that it could make for a constant. This means that x cannot be used where a constant expression is required.

For the basic types, which we’ll discuss next, the declaration syntax doesn’t change from what we’ve looked at so far. Things do change a bit for pointers, arrays, and functions, as you’ll see in the sections about those constructs.



Basic Types


Contents
  Declarations
  Basic Types
  Pointers
  Arrays
  Flow Control
  Functions
  Error Handling

  Printable version
  Discuss this article