- Linkage Attribute
- align Attribute
- deprecated Attribute
- Visibility Attribute
- Mutability Attributes
- Shared Storage Attributes
- @disable Attribute
- @safe, @trusted, and @system Attribute
- Function Attributes
- static Attribute
- auto Attribute
- scope Attribute
- OOP Attributes
- @mustuse Attribute
- User-Defined Attributes
Attributes
AttributeSpecifier: Attribute : Attribute DeclarationBlock Attribute: AlignAttribute AtAttribute DeprecatedAttribute FunctionAttributeKwd LinkageAttribute Pragma VisibilityAttribute abstract auto const final __gshared extern immutable inout override ref return scope shared static synchronized FunctionAttributeKwd: nothrow pure AtAttribute: @ disable @ nogc @ live Property @ safe @ system @ trusted UserDefinedAttribute Property: @ property DeclarationBlock: DeclDef { DeclDefsopt }
Attributes are a way to modify one or more declarations. The general forms are:
attribute declaration; // affects the declaration attribute: // affects all declarations until the end of // the current scope declaration; declaration; ... attribute { // affects all declarations in the block declaration; declaration; ... }
Linkage Attribute
LinkageAttribute: extern ( LinkageType ) extern ( C ++ , ) extern ( C ++ , QualifiedIdentifier ) extern ( C ++ , NamespaceList ) extern ( C ++ , class ) extern ( C ++ , struct ) LinkageType: C C ++ D Windows System Objective - C NamespaceList: ConditionalExpression ConditionalExpression, ConditionalExpression, NamespaceList
D provides an easy way to call C functions and operating system API functions, as compatibility with both is essential. The LinkageType is case sensitive, and is meant to be extensible by the implementation (they are not keywords). C and D must be supplied, the others are what makes sense for the implementation. C++ offers limited compatibility with C++, see the Interfacing to C++ documentation for more information. Objective-C offers compatibility with Objective-C, see the Interfacing to Objective-C documentation for more information. System is the same as Windows on Windows platforms, and C on other platforms.
Implementation Note: for Win32 platforms, Windows should exist.
C function calling conventions are specified by:
extern (C): int foo(); // call foo() with C conventionsNote that extern(C) can be provided for all types of declarations, including struct or class, even though there is no corresponding match on the C side. In that case, the attribute is ignored. This behavior applies for nested functions and nested variables as well. However, for static member methods and static nested functions, adding extern(C) will change the calling convention, but not the mangling.
D conventions are:
extern (D):
Windows API conventions are:
extern (Windows): void *VirtualAlloc( void *lpAddress, uint dwSize, uint flAllocationType, uint flProtect );
The Windows convention is distinct from the C convention only on Win32 platforms, where it is equivalent to the stdcall convention.
Note that a lone extern keyword is used as a storage class.
C++ Namespaces
The linkage form extern (C++, QualifiedIdentifier) creates C++ declarations that reside in C++ namespaces. The QualifiedIdentifier specifies the namespaces.
extern (C++, N) { void foo(); }
refers to the C++ declaration:
namespace N { void foo(); }
and can be referred to with or without qualification:
foo(); N.foo();
Namespaces create a new named scope that is imported into its enclosing scope.
extern (C++, N) { void foo(); void bar(); } extern (C++, M) { void foo(); } void main() { bar(); // ok //foo(); // error - N.foo() or M.foo() ? M.foo(); // ok }
Multiple dotted identifiers in the QualifiedIdentifier create nested namespaces:
extern (C++, N.M) { extern (C++) { extern (C++, R) { void foo(); } } } N.M.R.foo();
refers to the C++ declaration:
namespace N { namespace M { namespace R { void foo(); } } }
align Attribute
AlignAttribute: align align ( AssignExpression )
Specifies the alignment of:
- variables
- struct fields
- union fields
- class fields
- struct, union, and class types
align by itself sets it to the default, which matches the default member alignment of the companion C compiler.
struct S { align: byte a; // placed at offset 0 int b; // placed at offset 4 long c; // placed at offset 8 } static assert(S.alignof == 8); static assert(S.c.offsetof == 8); static assert(S.sizeof == 16);
The AssignExpression specifies the alignment which matches the behavior of the companion C compiler when non-default alignments are used. It must be a non-negative power of 2.
A value of 1 means that no alignment is done; fields are packed together.
struct S { align (1): byte a; // placed at offset 0 int b; // placed at offset 1 long c; // placed at offset 5 } static assert(S.alignof == 1); static assert(S.c.offsetof == 5); static assert(S.sizeof == 13);
The natural alignment of an aggregate is the maximum alignment of its fields. It can be overridden by setting the alignment outside of the aggregate.
align (2) struct S { align (1): byte a; // placed at offset 0 int b; // placed at offset 1 long c; // placed at offset 5 } static assert(S.alignof == 2); static assert(S.c.offsetof == 5); static assert(S.sizeof == 14);
Setting the alignment of a field aligns it to that power of 2, regardless of the size of the field.
struct S { byte a; // placed at offset 0 align (4) byte b; // placed at offset 4 align (16) short c; // placed at offset 16 } static assert(S.alignof == 16); static assert(S.c.offsetof == 16); static assert(S.sizeof == 32);
The AlignAttribute is reset to the default when entering a function scope or a non-anonymous struct, union, class, and restored when exiting that scope. It is not inherited from a base class.
See also: Struct Layout.
GC Compatibility
Do not align references or pointers that were allocated using NewExpression on boundaries that are not a multiple of size_t. The garbage collector assumes that pointers and references to GC allocated objects will be on size_t byte boundaries.
struct S { align(1): byte b; int* p; } static assert(S.p.offsetof == 1); @safe void main() { S s; s.p = new int; // error: can't modify misaligned pointer in @safe code }
deprecated Attribute
DeprecatedAttribute: deprecated deprecated ( AssignExpression )
It is often necessary to deprecate a feature in a library, yet retain it for backwards compatibility. Such declarations can be marked as deprecated, which means that the compiler can be instructed to produce an error if any code refers to deprecated declarations:
deprecated { void oldFoo(); } oldFoo(); // Deprecated: function test.oldFoo is deprecated
Optionally a string literal or manifest constant can be used to provide additional information in the deprecation message.
deprecated("Don't use bar") void oldBar(); oldBar(); // Deprecated: function test.oldBar is deprecated - Don't use bar
Calling CTFE-able functions or using manifest constants is also possible.
import std.format; enum message = format("%s and all its members are obsolete", Foobar.stringof); deprecated(message) class Foobar {} deprecated(format("%s is also obsolete", "This class")) class BarFoo {} void main() { auto fb = new Foobar(); // Deprecated: class test.Foobar is deprecated - Foobar // and all its members are obsolete auto bf = new BarFoo(); // Deprecated: class test.BarFoo is deprecated - This // class is also obsolete }
Implementation Note: The compiler should have a switch specifying if deprecated should be ignored, cause a warning, or cause an error during compilation.
Visibility Attribute
VisibilityAttribute: export package package ( QualifiedIdentifier ) private protected public
Visibility is an attribute that is one of private, package, protected, public, or export. They may be referred to as protection attributes in documents predating DIP22.
Visibility participates in symbol name lookup.
export Attribute
export means that a symbol can be accessed from outside the executable, shared library, or DLL. The symbol is said to be exported from where it is defined in an executable, shared library, or DLL, and imported by another executable, shared library, or DLL.
export applied to the definition of a symbol will export it. export applied to a declaration of a symbol will import it. A variable is a definition unless extern is applied to it.
export int x = 3; // definition, exporting `x` export int y; // definition, exporting `y` export extern int z; // declaration, importing `z` export __gshared h = 3; // definition, exporting `h` export __gshared i; // definition, exporting `i` export extern __gshared int j; // declaration, importing `j`
A function with a body is a definition, without a body is a declaration.
export void f() { } // definition, exporting `f` export void g(); // declaration, importing `g`
In Windows terminology, dllexport means exporting a symbol from a DLL, and dllimport means a DLL or executable is importing a symbol from a DLL.
package Attribute
package extends private so that package members can be accessed from code in other modules that are in the same package. If no identifier is provided, this applies to the innermost package only, or defaults to private if a module is not nested in a package.
package may have an optional parameter in the form of a dot-separated identifier list which is resolved as the qualified package name. The package must be either the module's parent package or one of its anscestors. If this parameter is present, the symbol will be visible in the specified package and all of its descendants.
private Attribute
Symbols with private visibility can only be accessed from within the same module. Private member functions are implicitly final and cannot be overridden.
protected Attribute
protected only applies inside classes (and templates as they can be mixed in) and means that a symbol can only be seen by members of the same module, or by a derived class. If accessing a protected instance member through a derived class member function, that member can only be accessed for the object instance which can be implicitly cast to the same type as ‘this’. protected module members are illegal.
public Attribute
public means that any code within the executable can see the member. It is the default visibility attribute.
Mutability Attributes
const Attribute
The const type qualifier changes the type of the declared symbol from T to const(T), where T is the type specified (or inferred) for the introduced symbol in the absence of const.
const int foo = 7; static assert(is(typeof(foo) == const(int))); const double bar = foo + 6; static assert(is(typeof(bar) == const(double)));
class C { const void foo(); const { void bar(); } void baz() const; } pragma(msg, typeof(C.foo)); // const void() pragma(msg, typeof(C.bar)); // const void() pragma(msg, typeof(C.baz)); // const void() static assert(is(typeof(C.foo) == typeof(C.bar)) && is(typeof(C.bar) == typeof(C.baz)));
See also: Methods Returning a Qualified Type.
immutable Attribute
The immutable attribute modifies the type from T to immutable(T), the same way as const does. See:
inout Attribute
The inout attribute modifies the type from T to inout(T), the same way as const does.
Shared Storage Attributes
shared Attribute
See shared.
__gshared Attribute
By default, non-immutable global declarations reside in thread local storage. When a global variable is marked with the __gshared attribute, its value is shared across all threads.
int foo; // Each thread has its own exclusive copy of foo. __gshared int bar; // bar is shared by all threads.
__gshared may also be applied to member variables and local variables. In these cases, __gshared is equivalent to static, except that the variable is shared by all threads rather than being thread local.
class Foo { __gshared int bar; } int foo() { __gshared int bar = 0; return bar++; // Not thread safe. }
Warning: Unlike the shared attribute, __gshared provides no safeguards against data races or other multi-threaded synchronization issues. It is the responsibility of the programmer to ensure that access to variables marked __gshared is synchronized correctly.
__gshared is disallowed in @safe code.
@synchronized Attribute
See Synchronized Classes.
@disable Attribute
A reference to a declaration marked with the @disable attribute causes a compile time error. This can be used to explicitly disallow certain operations or overloads at compile time rather than relying on generating a runtime error.
@disable void foo() { }
void main() { foo(); /* error, foo is disabled */ }
@disable this(); inside a struct disallows default construction.
Disabling a struct copy constructor makes the struct not copyable.
@safe, @trusted, and @system Attribute
See Function Safety.
Function Attributes
@nogc Attribute
See No-GC Functions.
@property Attribute
See Property Functions.
nothrow Attribute
See Nothrow Functions.
pure Attribute
See Pure Functions.
ref Attribute
See ref Storage Class.
return Attribute
static Attribute
The static attribute applies to types, functions and data. static is ignored when applied to other declarations.
Inside an aggregate type, a static declaration does not apply to a particular instance of an object, but to the type of the object. In other words, it means there is no this reference.
class Foo { static int x; static int bar() { return x; } int foobar() { return 7; } } Foo.x = 6; // no instance needed assert(Foo.bar() == 6); //Foo.foobar(); // error, no instance of Foo Foo f = new Foo; assert(f.bar() == 6); assert(f.foobar() == 7);
Static methods are never virtual.
Static data has one instance per thread, not one per object.
A static nested function or type cannot access variables in the parent scope.
Inside a function, a static local variable persists after the function returns.
Static does not have the additional C meaning of being local to a file. Use the private attribute in D to achieve that. For example:
module foo; int x = 3; // x is global private int y = 4; // y is local to module foo
auto Attribute
The auto attribute is used when there are no other attributes and type inference is desired.
auto i = 6.8; // declare i as a double
For functions, the auto attribute means return type inference. See Auto Functions.
scope Attribute
The scope attribute signifies a variable's pointer values will not escape the scope that the variable is declared in.
If the variable has a type that does not contain any indirections, the scope attribute is ignored.
When applied to a global variable, scope is also ignored, since there is no scope larger than global scope that pointers could escape to.
scope int* x; // scope ignored, global variable void main() { static int* x; // scope ignored, global variable scope float y; // scope ignored, no indirections scope int[2] z; // scope ignored, static array is value type scope int[] w; // scope dynamic array }
When applied to a local variable with a type that has indirections, its value may not be assigned to a variable with longer lifetime:
- Variables outside the Scope Statement that the variable is declared in
- Variables declared before the scope variable, since local variables are destructed in the reverse order that they are declared in
- __gshared or static variables
Other operations implicitly assigning them to variables with longer lifetime are also disallowed:
- Returning a scope variable from a function
- Assigning a scope variable to a non-scope parameter by calling a function
- Putting a scope variable in an array literal
The scope attribute is part of the variable declaration, not the type, and it only applies to the first level of indirection. For example, it is impossible to declare a variable as a dynamic array of scope pointers, because scope only applies to the .ptr of the array itself, not its elements. scope affects various types as follows:
Type of local variable | What scope applies to |
---|---|
Any Basic Data Type | nothing |
Pointer T* | the pointer value |
Dynamic Array T[] | the .ptr to the elements |
Static Array T[n] | each element T |
Associative Array K[V] | the pointer to the implementation defined structure |
struct or union | each of its member variables |
function pointer | the pointer value |
delegate | both the .funcptr and .ptr (closure context) pointer values |
class or interface | the class reference |
enum | the base type |
struct S { string str; // note: string = immutable(char)[] string* strPtr; } string escape(scope S s, scope S* sPtr, scope string[2] sarray, scope string[] darray) { return s.str; // invalid, scope applies to struct members return *s.strPtr; // valid, scope struct member is dereferenced return sPtr.str; // valid, struct pointer is dereferenced return *sPtr.strPtr; // valid, two pointers are dereferenced return sarray[0]; // invalid, scope applies to static array elements return sarray[1]; // invalid, ditto return darray[0]; // valid, scope applies to array pointer, not elements }
Scope Values
A "scope value" is the value of a scope variable, or a generated value pointing to stack allocated memory. Such values are generated by slicing a static array or creating a pointer to a variable that is (or may be) allocated on the stack:
- A function parameter
- A local variable
- A struct member accessed through the implicit this parameter
- The return value of a Ref Function
The variadic parameter from Typesafe Variadic Functions is also a scope value, since the arguments are passed on the stack.
When a local variable is assigned a scope value, it is inferred scope, even when the variable has an explicit type and does not use the auto keyword.
@safe: ref int[2] identity(return ref int[2] x) {return x;} int* escape(int[2] y, scope int* z) { int x; auto xPtr = &x; // inferred `scope int*` int[] yArr = identity(y)[]; // inferred `scope int[]` int* zCopy = z; // inferred `scope int*` return zCopy; // error } void variadic(int[] a...) { int[] x = a; // inferred `scope int[]` } void main() { variadic(1, 2, 3); }
struct S { int x; // this method may be called on a stack-allocated instance of S void f() { int* p = &x; // inferred `scope int* p` int* q = &this.x; // equivalent } }
Scope Parameters are treated the same as scope local variables, except that returning them is allowed when the function has Function Attribute Inference. In that case, they are inferred as Return Scope Parameters.
scope Class Instances
When used to allocate a class instance directly, a scope variable signifies the RAII (Resource Acquisition Is Initialization) protocol. This means that the destructor for an object is automatically called when the reference to it goes out of scope. The destructor is called even if the scope is exited via a thrown exception, thus scope is used to guarantee cleanup.
When a class is constructed with new and assigned to a local scope variable, it may be allocated on the stack and permitted in a @nogc context.
If there is more than one scope class variable going out of scope at the same point, then the destructors are called in the reverse order that the variables were constructed.
Assignment to a scope variable with class type, other than initialization, is not allowed, because that would complicate proper destruction of the variable.
import core.stdc.stdio : puts; class C { ~this() @nogc { puts(__FUNCTION__); } } void main() @nogc { { scope c0 = new C(); // allocated on the stack scope c1 = new C(); //c1 = c0; // Error: cannot rebind scope variables // destructor of `c1` and `c0` are called here in that order } puts("bye"); }
OOP Attributes
abstract Attribute
An abstract class must be overridden by a derived class. Declaring an abstract member function makes the class abstract.
final Attribute
- A class can be declared final to prevent subclassing.
- A class method can be declared final to prevent a derived class overriding it.
- Interfaces can define final methods.
override Attribute
See Virtual Functions.
@mustuse Attribute
The @mustuse attribute is a compiler-recognized UDA defined in the D runtime module core.attribute.
An expression is considered to be discarded if and only if either of the following is true:
- it is the top-level Expression in an ExpressionStatement, or
- it is the AssignExpression on the left-hand side of the comma in a CommaExpression.
It is a compile-time error to discard an expression if all of the following are true:
- it is not an assignment expression, an increment expression, or a decrement expression; and
- its type is a struct or union type whose declaration is annotated with @mustuse.
"Assignment expression" means either a simple assignment expression or an assignment operator expression.
"Increment expression" means a UnaryExpression or PostfixExpression whose operator is ++.
"Decrement expression" means a UnaryExpression or PostfixExpression whose operator is --.
It is a compile-time error to attach @mustuse to a function declaration or to any aggregate declaration other than a struct or union declaration. The purpose of this rule is to reserve such usage for possible future expansion.
User-Defined Attributes
UserDefinedAttribute: @ ( TemplateArgumentList ) @ TemplateSingleArgument @ Identifier ( NamedArgumentListopt ) @ TemplateInstance @ TemplateInstance ( NamedArgumentListopt )
User-Defined Attributes (UDA) are compile-time annotations that can be attached to a declaration. These attributes can then be queried, extracted, and manipulated at compile time. There is no runtime component to them.
A user-defined attribute is defined using:- Compile-time expressions
- A named manifest constant
- A type name
- A type to instantiate using a compile-time argument list
@(3) int a; // value argument @("string", 7) int b; // multiple values // using compile-time constant enum val = 3; @val int a2; // has same attribute as `a` enum Foo; @Foo int c; // type name attribute struct Bar { int x; } @Bar() int d; // type instance @Bar(3) int e; // type instance using initializer
For e, the attribute is an instance of struct Bar which is statically initialized using its argument.
If there are multiple UDAs in scope for a declaration, they are concatenated:
@(1) { @(2) int a; // has UDAs (1, 2) @("string") int b; // has UDAs (1, "string") }
A function parameter can have a UDA:
void f(@(3) int p);
__traits(getAttributes)
UDAs can be extracted into a compile-time sequence using __traits:
@('c') string s; pragma(msg, __traits(getAttributes, s)); // prints tuple('c')
If there are no user-defined attributes for the symbol, an empty sequence is returned. The result can be used just like any compile-time sequence - it can be indexed, passed as template parameters, etc.
enum e = 7; @("hello") struct SSS { } @(3) { @(4) @e @SSS int foo; } alias TP = __traits(getAttributes, foo); pragma(msg, TP); // prints tuple(3, 4, 7, (SSS)) pragma(msg, TP[2]); // prints 7
Any types in the sequence can be used to declare things:
TP[3] a; // a is declared as an SSS
The attribute of the type name is not the same as the attribute of the variable:
pragma(msg, __traits(getAttributes, a)); // prints tuple() pragma(msg, __traits(getAttributes, typeof(a))); // prints tuple("hello")
Usage
Of course, the real value of UDAs is to be able to create user-defined types with specific values. Having attribute values of basic types does not scale.
Whether the attributes are values or types is up to the user, and whether later attributes accumulate or override earlier ones is also up to how the user interprets them.
Templates
If a UDA is attached to a template declaration, then it will be automatically attached to all direct members of instances of that template. If any of those members are templates themselves, this rule applies recursively:
@("foo") template Outer(T) { struct S { int x; } int y; void fun() {} @("bar") template Inner(U) { int z; } } pragma(msg, __traits(getAttributes, Outer!int.S)); // prints tuple("foo") pragma(msg, __traits(getAttributes, Outer!int.S.x)); // prints tuple() pragma(msg, __traits(getAttributes, Outer!int.y)); // prints tuple("foo") pragma(msg, __traits(getAttributes, Outer!int.fun)); // prints tuple("foo") pragma(msg, __traits(getAttributes, Outer!int.Inner)); // prints tuple("foo", "bar") pragma(msg, __traits(getAttributes, Outer!int.Inner!int.z)); // prints tuple("foo", "bar")
UDAs cannot be attached to template parameters.