Improve this page Quickly fork, edit online, and submit a pull request for this page.
Requires a signed-in GitHub account. This works well for small changes.
If you'd like to make larger changes you may want to consider using
local clone.
Page wiki View or edit the community-maintained wiki page associated with this page.

Declaration Syntax

Declaration syntax generally reads right to left:

int x; // x is an int
int* x; // x is a pointer to int
int** x; // x is a pointer to a pointer to int
int[] x; // x is an array of ints
int*[] x; // x is an array of pointers to ints
int[]* x; // x is a pointer to an array of ints

Arrays read right to left as well:

int[3] x; // x is an array of 3 ints
int[3][5] x; // x is an array of 5 arrays of 3 ints
int[3]*[5] x; // x is an array of 5 pointers to arrays of 3 ints

Pointers to functions are declared using the function keyword:

intfunction(char) x; // x is a pointer to
// a function taking a char argument
// and returning an int
intfunction(char)[] x; // x is an array of
// pointers to functions
// taking a char argument
// and returning an int

int x[3]; // x is an array of 3 ints
int x[3][5]; // x is an array of 3 arrays of 5 ints
int (*x[5])[3]; // x is an array of 5 pointers to arrays of 3 ints
int (*x)(char); // x is a pointer to a function taking a char argument
// and returning an int
int (*[] x)(char); // x is an array of pointers to functions
// taking a char argument and returning an int

In a declaration declaring multiple symbols, all the declarations
must be of the same type:

The NonVoidInitializer cannot contain forward references
(this restriction may be removed in the future).
The implicitly inferred type is statically bound
to the declaration at compile time, not run time.

An ArrayLiteral
is inferred to be a dynamic array
type rather than a static array:

AliasDeclarations create a symbol that is an alias for another type,
and can be used anywhere that other type may appear.

alias myint = abc.Foo.bar;

Aliased types are semantically identical to the types they are aliased to. The
debugger cannot distinguish between them, and there is no difference as far as function
overloading is concerned. For example:

Variable declarations with the storage class extern are
not allocated storage within the module.
They must be defined in some other object file with a matching
name which is then linked in.
The primary usefulness of this is to connect with global
variable declarations in C files.

An extern declaration can optionally be followed by an
externlinkage attribute.
If there is no linkage attribute it defaults to extern(D):

Void Initializations

Normally, variables are initialized either with an explicit
Initializer or are set to the default value for the
type of the variable. If the Initializer is void,
however, the variable is not initialized. If its value is
used before it is set, undefined program behavior will result.

void foo()
{
int x = void;
writeln(x); // will print garbage
}

Therefore, one should only use void initializers as a
last resort when optimizing critical code.

Global and Static Initializers

The Initializer for a global or static variable must be
evaluatable at compile time.
Whether some pointers can be initialized with the addresses of other
functions or data is implementation defined.
Runtime initialization can be done with static constructors.

Type Qualifiers vs. Storage Classes

D draws a distinction between a type qualifer and a storage class.

A type qualifier creates a derived type from an existing base
type, and the resulting type may be used to create multiple instances
of that type.

For example, the immutable type qualifier can be used to
create variables of immutable type, such as:

A storage class, on the other hand, does not create a new
type, but describes only the type of storage used by the variable or
function being declared. For example, a member function can be declared
with the const storage class to indicate that it does not modify
its implicit this argument:

Although some keywords can be used both as a type qualifier and a
storage class, there are some storage classes that cannot be used to
construct new types. One example is ref:

// ref declares the parameter x to be passed by reference
void func(refint x)
{
x++; // so modifications to x will be visible in the caller
}
void main()
{
auto x = 1;
func(x);
assert(x == 2);
// However, ref is not a type qualifier, so the following is illegal:
ref(int) y; // Error: ref is not a type qualifier.
}

// Functions can also be declared as 'ref', meaning their return value is
// passed by reference:
refint func2()
{
staticint y = 0;
return y;
}
void main()
{
func2() = 2; // The return value of func2() can be modified.
assert(func2() == 2);
// However, the reference returned by func2() does not propagate to
// variables, because the 'ref' only applies to the return value itself,
// not to any subsequent variable created from it:
auto x = func2();
staticassert(typeof(x) == int); // N.B.: *not* ref(int);
// there is no such type as ref(int).
x++;
assert(x == 3);
assert(func2() == 2); // x is not a reference to what func2() returned; it
// does not inherit the ref storage class from func2().
}

Due to the fact that some keywords, such as const, can be used
both as a type qualifier and a storage class, it may sometimes result
in ambiguous-looking code:

To avoid such confusion, it is recommended that type qualifier
syntax with parentheses always be used for return types, and that
function storage classes be written on the right-hand side of the
declaration instead of the left-hand side where it may be visually
confused with the return type:

struct S
{
// Now it is clear that the 'const' here applies to the return type:
const(int) func1() { return 1; }
// And it is clear that the 'const' here applies to the function:
int func2() const { return 1; }
}