Declarations - D Programming Language

Menu

- Learn
- Documentation
  - Language Reference
  - Library Reference
  - Command-line Reference
  - Feature Overview
  - Articles
- Downloads
- Packages
- Community
  - Blog
  - Orgs using D
  - Twitter
  - Forums
  - IRC
  - Community Discord
  - Wiki
  - GitHub
  - Issues
  - Get involved
  - Contributors
  - Foundation
  - Security Team
  - Donate
  - Sponsors
- Resources
  - Tour
  - Books
  - Tutorials
  - Tools
  - Editors
  - IDEs
  - run.dlang.io
  - Visual D
  - Acknowledgments
  - D Style
  - Glossary
  - Sitemap

Search
Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
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 a local clone.

# Declarations

Contents

1. Grammar
   1. Aggregates
   2. Variable Declarations
   3. Storage Classes
   4. Initializers
2. Declaration Syntax
   1. Pointers to Functions
   2. C-Style Declarations
   3. Declaring Multiple Symbols
3. Implicit Type Inference
4. Alias Declarations
   1. Type Aliases
   2. Symbol Aliases
   3. Aliasing an Overload Set
   4. Aliasing Variables
   5. Aliasing a Function Type
   6. Alias Assign
   7. Alias Reassignment
5. Extern Declarations
6. Void Initializations
7. Global and Static Initializers
8. Type Qualifiers vs. Storage Classes
   1. ref Storage Class
   2. Methods Returning a Qualified Type

## Grammar

    Declaration:
        FuncDeclaration
        VarDeclarations
        AliasDeclaration
        AliasAssign
        AggregateDeclaration
        EnumDeclaration
        ImportDeclaration
        ConditionalDeclaration
        StaticForeachDeclaration
        StaticAssert

See also DeclDef.

### Aggregates

    AggregateDeclaration:
        ClassDeclaration
        InterfaceDeclaration
        StructDeclaration
        UnionDeclaration

### Variable Declarations

    VarDeclarations:
        StorageClassesopt BasicType Declarators ;
        AutoDeclaration

    Declarators:
        DeclaratorInitializer
        DeclaratorInitializer , DeclaratorIdentifierList

    DeclaratorInitializer:
        VarDeclarator
        VarDeclarator TemplateParametersopt = Initializer

    DeclaratorIdentifierList:
        DeclaratorIdentifier
        DeclaratorIdentifier , DeclaratorIdentifierList

    DeclaratorIdentifier:
        Identifier
        Identifier TemplateParametersopt = Initializer

    Declarator:
        VarDeclarator

    VarDeclarator:
        TypeSuffixesopt Identifier

### Storage Classes

See Type Classes vs. Storage Classes.

    StorageClasses:
        StorageClass
        StorageClass StorageClasses

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

### Initializers

    Initializer:
        VoidInitializer
        NonVoidInitializer

    NonVoidInitializer:
        AssignExpression
        ArrayLiteral
        StructInitializer

See also VoidInitializer.

## 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

An AliasDeclaration creates a symbol name that refers to a type or another symbol. That name can then be used anywhere that the target may appear. The following can be aliased:

- Types
  - Function Types (with default arguments)
- Variables
- Manifest Constants
- Modules
- Packages
- Functions
- Overload Sets
- Function Literals
- Templates
- Template Instantiations
- Other Alias Declarations

### Type Aliases

    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

Type aliases can sometimes look indistinguishable from other symbol aliases:

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

Best Practices: Other than when aliasing simple basic type names, type alias names should be Capitalized.

### Symbol Aliases

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

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 imported module or package into the current scope:

    static import string;
    ...
    alias strlen = string.strlen;

### Aliasing an Overload Set

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

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

    class C : B
    {
        // declaring an overload hides any base class overloads
        int foo(int a) { return 3; }
        // redeclare hidden overload
        alias foo = B.foo;
    }

    void main()
    {
        import std.stdio;

        C c = new C();
        c.foo(1, 2u).writeln;   // calls B.foo
        c.foo(1).writeln;       // calls C.foo
    }

### Aliasing Variables

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`
    assert(S.i == 2);
    assert(S.j == 4);

### Aliasing a Function Type

Function types can be aliased:

    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));

Aliases can be used to call a function with different default arguments, change an argument from required to default or vice versa:

    import std.stdio : writefln;

    void main() {
        Foo foo = &foofoo;
        foo();              // prints v: 6
        foo(8);             // prints v: 8
        Bar bar = &barbar;
        // bar(4);             // compilation error, because the `Bar` alias
                            // requires an explicit 2nd argument
        barbar(4);          // prints a: 4, b: 6, c: 7
        bar(4, 5);          // prints a: 4, b: 5, c: 9
        bar(4, 5, 6);       // prints a: 4, b: 5, c: 6

        Baz baz = &barbar;
        baz();              // prints a: 2, b: 3, c: 4
    }

    alias Foo = void function(int=6);
    alias Bar = void function(int, int, int=9);
    alias Baz = void function(int=2, int=3, int=4);

    void foofoo(int v = 6) {
        writefln("v: %d", v);
    }

    void barbar(int a, int b = 6, int c = 7) {
        writefln("a: %d, b: %d, c: %d", a, b, c);
    }

### Alias Assign

    AliasAssign:
        Identifier = Type

An AliasDeclaration can have a new value assigned to it with an AliasAssign:

    template Gorgon(T)
    {
        alias A = long;
        A = T; // assign new value to A
        alias Gorgon = A;
    }
    pragma(msg, Gorgon!int); // prints int

- The AliasAssign and its corresponding AliasDeclaration must both be declared in the same TemplateDeclaration.
- The corresponding AliasDeclaration must appear lexically before the AliasAssign.
- The corresponding AliasDeclaration may not refer to overloaded symbols.
- The value of an AliasDeclaration or left hand side (lvalue) of an AliasAssign may not be used prior to another AliasAssign to the same lvalue other than in the right hand side of that AliasAssign.

Best Practices: AliasAssign is particularly useful when using an iterative computation rather than a recursive one, as it avoids creating the large number of intermediate templates that the recursive one engenders.

    import std.meta : AliasSeq;

    static if (0) // recursive method for comparison
    {
        template Reverse(T...)
        {
            static if (T.length == 0)
                alias Reverse = AliasSeq!();
            else
                alias Reverse = AliasSeq!(Reverse!(T[1 .. T.length]), T[0]);
        }
    }
    else // iterative method minimizes template instantiations
    {
        template Reverse(T...)
        {
            alias A = AliasSeq!();
            static foreach (t; T)
                A = AliasSeq!(t, A); // Alias Assign
            alias Reverse = A;
        }
    }

    enum X = 3;
    alias TK = Reverse!(int, const uint, X);
    pragma(msg, TK); // prints tuple(3, (const(uint)), (int))

### Alias Reassignment

    AliasReassignment:
        Identifier = StorageClassesopt Type
        Identifier = FunctionLiteral
        Identifier = StorageClassesopt BasicType Parameters MemberFunctionAttributesopt

An alias declaration inside a template can be reassigned a new value.

    import std.meta : AliasSeq;

    template staticMap(alias F, Args...)
    {
        alias A = AliasSeq!();
        static foreach (Arg; Args)
            A = AliasSeq!(A, F!Arg); // alias reassignment
        alias staticMap = A;
    }

    enum size(T) = T.sizeof;
    static assert(staticMap!(size, char, wchar, dchar) == AliasSeq!(1, 2, 4));

The Identifier must resolve to a lexically preceding AliasDeclaration. Both must be members of the same TemplateDeclaration.

The right hand side of the AliasReassignment replaces the right hand side of the AliasDeclaration.

Once the AliasDeclaration has been referred to in any context other than the right hand side of an AliasReassignment it can no longer be reassigned.

Rationale: Alias reassignment can result in faster compile times and lowered memory consumption, and requires significantly simpler code than the alternative recursive method.

## 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. Void initializers for objects that contain indirections is not allowed in @safe code.

Implementation Defined: If a void initialized variable's value is used before it is set, its value is implementation defined.

    void bad()
    {
        int x = void;
        writeln(x);  // print implementation defined value
    }

Undefined Behavior: If a void initialized variable's value is used before it is set, and the value is a reference, pointer or an instance of a struct with an invariant, the behavior is undefined.

    void muchWorse()
    {
        char[] p = void;
        writeln(p);  // may result in apocalypse
    }

Best Practices:

1. Void initializers are useful when a static array is on the stack, but may only be partially used, such 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 qualifers and storage classes are distinct concepts.

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 as both a type qualifier and a storage class, there are some storage classes that cannot be used to construct new types, such as ref.

### ref Storage Class

A parameter declared with ref is passed by reference:

    void func(ref int i)
    {
        i++; // modifications to i 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().
    }

### Methods Returning a Qualified Type

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*
            return null;
        }

        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; }
    }

Modules
Types
Copyright © 1999-2023 by the D Language Foundation | Page generated by Ddoc on Sun Jul 2 00:31:01 2023