Declarations

Contents
  1. Declaration Syntax
    1. Pointers to Functions
    2. C-Style Declarations
    3. Declaring Multiple Symbols
  2. Implicit Type Inference
  3. Alias Declarations
  4. Extern Declarations
  5. Void Initializations
  6. Global and Static Initializers
  7. Type Qualifiers vs. Storage Classes
Declaration:
    FuncDeclaration
    VarDeclarations
    AliasDeclaration
    AggregateDeclaration
    EnumDeclaration
    ImportDeclaration
    ConditionalDeclaration
    StaticForeachDeclaration
    StaticAssert
VarDeclarations:
    StorageClassesopt BasicType Declarators ;
    AutoDeclaration

Declarators:
    DeclaratorInitializer
    DeclaratorInitializer , DeclaratorIdentifierList

DeclaratorInitializer:
    VarDeclarator
    VarDeclarator TemplateParametersopt = Initializer
    AltDeclarator
    AltDeclarator = Initializer

DeclaratorIdentifierList:
    DeclaratorIdentifier
    DeclaratorIdentifier , DeclaratorIdentifierList

DeclaratorIdentifier:
    VarDeclaratorIdentifier
    AltDeclaratorIdentifier

VarDeclaratorIdentifier:
    Identifier
    Identifier TemplateParametersopt = Initializer

AltDeclaratorIdentifier:
    TypeSuffixes Identifier AltDeclaratorSuffixesopt
    TypeSuffixes Identifier AltDeclaratorSuffixesopt = Initializer
    TypeSuffixesopt Identifier AltDeclaratorSuffixes
    TypeSuffixesopt Identifier AltDeclaratorSuffixes = Initializer

Declarator:
    VarDeclarator
    AltDeclarator

VarDeclarator:
    TypeSuffixesopt Identifier

AltDeclarator:
    TypeSuffixesopt Identifier AltDeclaratorSuffixes
    TypeSuffixesopt ( AltDeclaratorInner )
    TypeSuffixesopt ( AltDeclaratorInner ) AltFuncDeclaratorSuffix
    TypeSuffixesopt ( AltDeclaratorInner ) AltDeclaratorSuffixes

AltDeclaratorInner:
    TypeSuffixesopt Identifier
    TypeSuffixesopt Identifier AltFuncDeclaratorSuffix
    AltDeclarator

AltDeclaratorSuffixes:
    AltDeclaratorSuffix
    AltDeclaratorSuffix AltDeclaratorSuffixes

AltDeclaratorSuffix:
    [ ]
    [ AssignExpression ]
    [ Type ]

AltFuncDeclaratorSuffix:
    Parameters MemberFunctionAttributesopt
StorageClasses:
    StorageClass
    StorageClass StorageClasses

StorageClass:
    LinkageAttribute
    AlignAttribute
    deprecated
    enum
    static
    extern
    abstract
    final
    override
    synchronized
    auto
    scope
    const
    immutable
    inout
    shared
    __gshared
    Property
    nothrow
    pure
    ref
Initializer:
    VoidInitializer
    NonVoidInitializer

NonVoidInitializer:
    ExpInitializer
    ArrayInitializer
    StructInitializer

ExpInitializer:
    AssignExpression

ArrayInitializer:
    [ ArrayMemberInitializationsopt ]

ArrayMemberInitializations:
    ArrayMemberInitialization
    ArrayMemberInitialization ,
    ArrayMemberInitialization , ArrayMemberInitializations

ArrayMemberInitialization:
    NonVoidInitializer
    AssignExpression : NonVoidInitializer

StructInitializer:
    { StructMemberInitializersopt }

StructMemberInitializers:
    StructMemberInitializer
    StructMemberInitializer ,
    StructMemberInitializer , StructMemberInitializers

StructMemberInitializer:
    NonVoidInitializer
    Identifier : NonVoidInitializer

Declaration Syntax

Declaration syntax generally reads right to left, including arrays:

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

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

Pointers to Functions

Pointers to functions are declared using the function keyword:

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

C-Style Declarations

C-style array, function pointer and pointer to array declarations are deprecated:

int x[3];          // x is a static array of 3 ints
int x[3][5];       // x is a static array of 3 arrays of 5 ints

int (*x[5])[3];    // x is a static array of 5 pointers to static 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

Declaring Multiple Symbols

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

int x,y;   // x and y are ints
int* x,y;  // x and y are pointers to ints
int[] x,y; // x and y are arrays of ints

// invalid C-style declarations
int x,*y;  // error, multiple types
int x[],y; // error, multiple types

Implicit Type Inference

AutoDeclaration:
    StorageClasses AutoAssignments ;

AutoAssignments:
    AutoAssignment
    AutoAssignments , AutoAssignment

AutoAssignment:
    Identifier TemplateParametersopt = Initializer

If a declaration starts with a StorageClass and has a NonVoidInitializer from which the type can be inferred, the type on the declaration can be omitted.

static x = 3;      // x is type int
auto y = 4u;       // y is type uint

auto s = "Apollo"; // s is type immutable(char)[] i.e., string

class C { ... }

auto c = new C();  // c is a handle to an instance of class C

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:

auto v = ["resistance", "is", "useless"]; // type is string[], not string[3]

Alias Declarations

AliasDeclaration:
    alias StorageClassesopt BasicType Declarators ;
    alias StorageClassesopt BasicType FuncDeclarator ;
    alias AliasAssignments ;

AliasAssignments:
    AliasAssignment
    AliasAssignments , AliasAssignment

AliasAssignment:
    Identifier TemplateParametersopt = StorageClassesopt Type
    Identifier TemplateParametersopt = FunctionLiteral
    Identifier TemplateParametersopt = StorageClassesopt BasicType Parameters MemberFunctionAttributesopt

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:

alias myint = int;

void foo(int x) { ... }
void foo(myint m) { ... } // error, multiply defined function foo

A symbol can be declared as an alias of another symbol. For example:

import planets;

alias myAlbedo = planets.albedo;
...
int len = myAlbedo("Saturn"); // actually calls planets.albedo()

The following alias declarations are valid:

template Foo2(T) { alias t = T; }
alias t1 = Foo2!(int);
alias t2 = Foo2!(int).t;
alias t3 = t1.t;
alias t4 = t2;

t1.t v1;  // v1 is type int
t2 v2;    // v2 is type int
t3 v3;    // v3 is type int
t4 v4;    // v4 is type int

alias Fun = int(string p);
int fun(string){return 0;}
static assert(is(typeof(fun) == Fun));

alias MemberFun1 = int() const;
alias MemberFun2 = const int();
// leading attributes apply to the func, not the return type
static assert(is(MemberFun1 == MemberFun2));

Aliased symbols are useful as a shorthand for a long qualified symbol name, or as a way to redirect references from one symbol to another:

version (Win32)
{
    alias myfoo = win32.foo;
}
version (linux)
{
    alias myfoo = linux.bar;
}

Aliasing can be used to import a symbol from an import into the current scope:

alias strlen = string.strlen;

Aliases can also import a set of overloaded functions, that can be overloaded with functions in the current scope:

class A
{
    int foo(int a) { return 1; }
}

class B : A
{
    int foo( int a, uint b ) { return 2; }
}

class C : B
{
    int foo( int a ) { return 3; }
    alias foo = B.foo;
}

class D : C
{
}

void test()
{
    D b = new D();
    int i;

    i = b.foo(1, 2u);   // calls B.foo
    i = b.foo(1);       // calls C.foo
}

Note: Type aliases can sometimes look indistinguishable from alias declarations:

alias abc = foo.bar; // is it a type or a symbol?

The distinction is made in the semantic analysis pass.

Aliases cannot be used for expressions:

struct S
{
    static int i;
    static int j;
}

alias a = S.i; // OK, `S.i` is a symbol
alias b = S.j; // OK. `S.j` is also a symbol
alias c = a + b; // illegal, `a + b` is an expression
a = 2;         // sets `S.i` to `2`
b = 4;         // sets `S.j` to `4`

Extern Declarations

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.

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

// variable allocated and initialized in this module with C linkage
extern(C) int foo;
// variable allocated outside this module with C linkage
// (e.g. in a statically linked C library or another module)
extern extern(C) int bar;
Best Practices:
  1. The primary usefulness of Extern Declarations is to connect with global variables declarations and functions in C or C++ files.

Void Initializations

VoidInitializer:
    void

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.

Undefined Behavior: If a void initialized variable's value is used before it is set, the behavior is undefined.
void foo()
{
    int x = void;
    writeln(x);  // will print garbage
}
Best Practices:
  1. Void initializers are useful when a static array is on the stack, but may only be partially used, such as as a temporary buffer. Void initializers will potentially speed up the code, but they introduce risk, since one must ensure that array elements are always set before read.
  2. The same is true for structs.
  3. Use of void initializers is rarely useful for individual local variables, as a modern optimizer will remove the dead store of its initialization if it is initialized later.
  4. For hot code paths, it is worth profiling to see if the void initializer actually improves results.

Global and Static Initializers

The Initializer for a global or static variable must be evaluatable at compile time. Runtime initialization is done with static constructors.

Implementation Defined:
  1. Whether some pointers can be initialized with the addresses of other functions or data.

Type Qualifiers vs. Storage Classes

Type qualifer and storage classes are distinct.

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:

immutable(int)   x; // typeof(x) == immutable(int)
immutable(int)[] y; // typeof(y) == immutable(int)[]
                    // typeof(y[0]) == immutable(int)

// Type constructors create new types that can be aliased:
alias ImmutableInt = immutable(int);
ImmutableInt z;     // typeof(z) == immutable(int)

A storage class, on the other hand, does not create a new type, but describes only the kind 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:

struct S
{
    int x;
    int method() const
    {
        //x++;    // Error: this method is const and cannot modify this.x
        return x; // OK: we can still read this.x
    }
}

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, such as ref:

// ref declares the parameter x to be passed by reference
void func(ref int 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:

ref int func2()
{
    static int 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();
    static assert(is(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().
}

Some keywords, such as const, can be used both as a type qualifier and a storage class. The distinction is determined by the syntax where it appears.

struct S
{
    /* Is const here a type qualifier or a storage class?
     * Is the return value const(int), or is this a const function that returns
     * (mutable) int?
     */
    const int* func() // a const function
    {
        ++p;          // error, this.p is const
        return p;     // error, cannot convert const(int)* to int*
    }

    const(int)* func() // a function returning a pointer to a const int
    {
        ++p;          // ok, this.p is mutable
        return p;     // ok, int* can be implicitly converted to const(int)*
    }

    int* p;
}
Best Practices: To avoid confusion, the type qualifier syntax with parentheses should be used for return types, and function storage classes should 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; }
}

© 1999–2021 The D Language Foundation
Licensed under the Boost License 1.0.
https://dlang.org/spec/declaration.html