Table of Contents

This is the specification for the D Programming Language. For more information see dlang.org.

- Introduction

- Lexical

- Interpolation Expression Sequence

- Grammar

- Modules

- Declarations

- Types

- Properties

- Attributes

- Pragmas

- Expressions

- Statements

- Arrays

- Associative Arrays

- Structs and Unions

- Classes

- Interfaces

- Enums

- Type Qualifiers

- Functions

- Operator Overloading

- Templates

- Template Mixins

- Contract Programming

- Conditional Compilation

- Traits

- Error Handling

- Unit Tests

- Garbage Collection

- Floating Point

- D x86 Inline Assembler

- Embedded Documentation

- Interfacing to C

- Interfacing to C++

- Interfacing to Objective-C

- Portability Guide

- Named Character Entities

- Memory Safety

- Application Binary Interface

- Vector Extensions

- Better C

- Import C

- Live Functions

- Windows Programming

- Glossary

- Legacy Code

Introduction

D is a general-purpose systems programming language with a C-like syntax that compiles to native code. It is statically typed and supports both automatic (garbage collected) and manual memory management. D programs are structured as modules that can be compiled separately and linked with external libraries to create native libraries or executables.

This document is the reference manual for the D Programming Language. For more information and other documents, see The D Language Website.

Phases of Compilation

The process of compiling is divided into multiple phases. Each phase is independent of subsequent phases. For example, the scanner is not affected by the semantic analyzer. This separation of passes makes language tools like syntax-directed editors relatively easy to create.

1. source character set
The source file is checked to determine its encoding and the appropriate scanner is loaded. 7-bit ASCII and UTF encodings are accepted.

2. script line
If the first line starts with "#!", then that line is ignored.

3. lexical analysis
The source file is divided into a sequence of tokens. Special tokens are replaced with other tokens. SpecialTokenSequences are processed and removed.

4. syntax analysis
The sequence of tokens is parsed to form syntax trees.

5. semantic analysis
The syntax trees are traversed to declare variables, load symbol tables, assign types, and determine the meaning of the program.

6. optimization
Optimization is an optional pass that attempts to rewrite the program in a semantically equivalent, more performant, version.

7. code generation
Instructions are selected from the target architecture to implement the semantics of the program. The typical result will be an object file suitable for input to a linker.

Memory Model

The byte is the fundamental unit of storage. Each byte has 8 bits and is stored at a unique address. A memory location is a sequence of one or more bytes of the exact size required to hold a scalar type. Multiple threads can access separate memory locations without interference.

Memory locations come in three groups:

1. Thread-local memory locations are accessible from only one thread at a time.

2. Immutable memory locations cannot be written to during their lifetime. Immutable memory locations can be read from by multiple threads without synchronization.

3. Shared memory locations are accessible from multiple threads.

Undefined Behavior: Allowing multiple threads to access a thread-local memory location results in undefined behavior.

Undefined Behavior: Writing to an immutable memory location during its lifetime results in undefined behavior.

Undefined Behavior: Writing to a shared memory location in one thread while one or more additional threads read from or write to the same location is undefined behavior unless all of the reads and writes are synchronized.

Execution of a single thread on thread-local and immutable memory locations is sequentially consistent. This means the collective result of the operations is the same as if they were executed in the same order that the operations appear in the program.

A memory location can be transferred from thread-local to immutable or shared if there is only one reference to the location.

A memory location can be transferred from shared to immutable or thread-local if there is only one reference to the location.

A memory location can be temporarily transferred from shared to local if synchronization is used to prevent any other threads from accessing the memory location during the operation.

Object Model

An object is created in the following circumstances:

- a definition

- a NewExpression

- a temporary is created

- changing which field of a union is active

An object spans a sequence of memory locations which may or may not be contiguous. Its lifetime encompasses construction, destruction, and the period in between. Each object has a type which is determined either statically or by runtime type information. The object's memory locations may include any combination of thread-local, immutable, or shared.

Objects can be composed into a composed object. Objects that make up a composed object are subobjects. An object that is not the subobject of another object is a complete object. The lifetime of a subobject is always within the lifetime of the complete object to which it belongs.

An object's address is the address of the first byte of the first memory location for that object. Object addresses are distinct unless one object is nested within the other.

Arithmetic

Integer Arithmetic

Integer arithmetic is performed using two's complement math. Integer overflow is not checked for.

Floating Point Arithmetic

Floating point arithmetic is performed using IEEE-754 floating point math.

Lexical

The lexical analysis is independent of the syntax parsing and the semantic analysis. The lexical analyzer splits the source text into tokens. The lexical grammar describes the syntax of these tokens. The grammar is designed to be suitable for high-speed scanning and to facilitate the implementation of a correct scanner. It has a minimum of special case rules and there is only one phase of translation.

Source Text

SourceFile:
ByteOrderMark Moduleopt
Shebang Moduleopt
Moduleopt

ByteOrderMark:
\uFEFF

Shebang:
#! Charactersopt EndOfShebang

EndOfShebang:
\u000A
EndOfFile

Source text can be encoded as any one of the following:

- ASCII (strictly, 7-bit ASCII)

- UTF-8

- UTF-16BE

- UTF-16LE

- UTF-32BE

- UTF-32LE

One of the following UTF BOMs (Byte Order Marks) can be present at the beginning of the source text:

UTF Byte Order Marks

Format

BOM

UTF-8

EF BB BF

UTF-16BE

FE FF

UTF-16LE

FF FE

UTF-32BE

00 00 FE FF

UTF-32LE

FF FE 00 00

ASCII

no BOM

If the source file does not begin with a BOM, then the first character must be less than or equal to U+0000007F.

The source text is decoded from its source representation into Unicode Characters. The Characters are further divided into: WhiteSpace, EndOfLine, Comments, SpecialTokenSequences, and Tokens, with the source terminated by an EndOfFile.

The source text is split into tokens using the maximal munch algorithm, i.e., the lexical analyzer assumes the longest possible token. For example, >> is a right-shift token rather than two greater-than tokens. There are two exceptions to this rule:

- A .. embedded between what appear to be two floating point literals, as in 1..2, is interpreted as if the .. were separated by a space from the first integer.

- A 1.a is interpreted as the three tokens 1, ., and a, whereas 1. a is interpreted as the two tokens 1. and a.

Character Set

Character:
any Unicode character

End of File

EndOfFile:
physical end of the file
\u0000
\u001A

The source text is terminated by whichever comes first.

End of Line

EndOfLine:
\u000D
\u000A
\u000D \u000A
\u2028
\u2029
EndOfFile

White Space

WhiteSpace:
Space
Space WhiteSpace

Space:
\u0020
\u0009
\u000B
\u000C

Comments

Comment:
BlockComment
LineComment
NestingBlockComment

BlockComment:
/* Charactersopt */

LineComment:
// Charactersopt EndOfLine

NestingBlockComment:
/+ NestingBlockCommentCharactersopt +/

NestingBlockCommentCharacters:
NestingBlockCommentCharacter
NestingBlockCommentCharacter NestingBlockCommentCharacters

NestingBlockCommentCharacter:
Character
NestingBlockComment

Characters:
Character
Character Characters

There are three kinds of comments:

1. Block comments can span multiple lines, but do not nest.

2. Line comments terminate at the end of the line.

3. Nesting block comments can span multiple lines and can nest.

The contents of strings and comments are not tokenized. Consequently, comment openings occurring within a string do not begin a comment, and string delimiters within a comment do not affect the recognition of comment closings and nested /+ comment openings. With the exception of /+ occurring within a /+ comment, comment openings within a comment are ignored.

a = /+ // +/ 1;    // parses as if 'a = 1;'
a = /+ "+/" +/ 1"; // parses as if 'a = " +/ 1";'
a = /+ /* +/ */ 3; // parses as if 'a = */ 3;'

Comments cannot be used as token concatenators, for example, abc/**/def is two tokens, abc and def, not one abcdef token.

Tokens

Tokens:
Token
Token Tokens

Token:
{
}
TokenNoBraces

TokenNoBraces:
Identifier
StringLiteral
InterpolationExpressionSequence
CharacterLiteral
IntegerLiteral
FloatLiteral
Keyword
/
/=
.
..
...
&
&=
&&
|
|=
||
-
-=
--
+
+=
++
<
<=
<<
<<=
>
>=
>>=
>>>=
>>
>>>
!
!=
(
)
[
]
?
,
;
:
$
=
==
*
*=
%
%=
^
^=
^^
^^=
~
~=
@
=>

Identifiers

Identifier:
IdentifierStart
IdentifierStart IdentifierChars

IdentifierChars:
IdentifierChar
IdentifierChar IdentifierChars

IdentifierStart:
_
Letter
UniversalAlpha

IdentifierChar:
IdentifierStart
0
NonZeroDigit

Identifiers start with a letter, _, or universal alpha, and are followed by any number of letters, _, digits, or universal alphas. Universal alphas are as defined in ISO/IEC 9899:1999(E) Appendix D of the C99 Standard. Identifiers can be arbitrarily long, and are case sensitive.

Implementation Defined: Identifiers starting with __ (two underscores) are reserved.

String Literals

StringLiteral:
WysiwygString
AlternateWysiwygString
DoubleQuotedString
DelimitedString
TokenString
HexString

A string literal is either a wysiwyg quoted string, a double quoted string, a delimited string, a token string, or a hex string.

In all string literal forms, an EndOfLine is regarded as a single \n character.

Wysiwyg Strings

WysiwygString:
r" WysiwygCharactersopt " StringPostfixopt

AlternateWysiwygString:
` WysiwygCharactersopt ` StringPostfixopt

WysiwygCharacters:
WysiwygCharacter
WysiwygCharacter WysiwygCharacters

WysiwygCharacter:
Character
EndOfLine

Wysiwyg ("what you see is what you get") quoted strings can be defined using either of two syntaxes.

In the first form, they are enclosed between r" and ". All characters between the r" and " are part of the string. There are no escape sequences inside wysiwyg strings.

r"I am Oz"
r"c:\games\Sudoku.exe"
r"ab\n" // string is 4 characters,
// 'a', 'b', '\', 'n'

Alternatively, wysiwyg strings can be enclosed by backquotes, using the ` character.

`the Great and Powerful.`
`c:\games\Empire.exe`
`The "lazy" dog`
`a"b\n`  // string is 5 characters,
// 'a', '"', 'b', '\', 'n'

See also InterpolatedWysiwygLiteral

Double Quoted Strings

DoubleQuotedString:
" DoubleQuotedCharactersopt " StringPostfixopt

DoubleQuotedCharacters:
DoubleQuotedCharacter
DoubleQuotedCharacter DoubleQuotedCharacters

DoubleQuotedCharacter:
Character
EscapeSequence
EndOfLine

Double quoted strings are enclosed by "". EscapeSequences can be embedded in them.

"Who are you?"
"c:\\games\\Doom.exe"
"ab\n"   // string is 3 characters,
// 'a', 'b', and a linefeed
"ab
"        // string is 3 characters,
// 'a', 'b', and a linefeed

See also InterpolatedDoubleQuotedLiteral

Delimited Strings

DelimitedString:
q" Delimiter WysiwygCharactersopt MatchingDelimiter " StringPostfixopt
q"( ParenDelimitedCharactersopt )" StringPostfixopt
q"[ BracketDelimitedCharactersopt ]" StringPostfixopt
q"{ BraceDelimitedCharactersopt }" StringPostfixopt
q"< AngleDelimitedCharactersopt >" StringPostfixopt

Delimiter:
Identifier

MatchingDelimiter:
Identifier

ParenDelimitedCharacters:
WysiwygCharacter
WysiwygCharacter ParenDelimitedCharacters
( ParenDelimitedCharactersopt )

BracketDelimitedCharacters:
WysiwygCharacter
WysiwygCharacter BracketDelimitedCharacters
[ BracketDelimitedCharactersopt ]

BraceDelimitedCharacters:
WysiwygCharacter
WysiwygCharacter BraceDelimitedCharacters
{ BraceDelimitedCharactersopt }

AngleDelimitedCharacters:
WysiwygCharacter
WysiwygCharacter AngleDelimitedCharacters
< AngleDelimitedCharactersopt >

Delimited strings use various forms of delimiters. The delimiter, whether a character or identifier, must immediately follow the " without any intervening whitespace. The terminating delimiter must immediately precede the closing " without any intervening whitespace. A nesting delimiter nests, and is one of the following characters:

Nesting Delimiters

Delimiter

Matching Delimiter

[

]

(

)

<

>

{

}

q"(foo(xxx))"   // "foo(xxx)"
q"[foo{]"       // "foo{"

If the delimiter is an identifier, the identifier must be immediately followed by a newline, and the matching delimiter must be the same identifier starting at the beginning of the line:

writeln(q"EOS
This
is a multi-line
heredoc string
EOS"
);

The newline following the opening identifier is not part of the string, but the last newline before the closing identifier is part of the string. The closing identifier must be placed on its own line at the leftmost column.

Otherwise, the matching delimiter is the same as the delimiter character:

q"/foo]/"          // "foo]"
// q"/abc/def/"    // error

Token Strings

TokenString:
q{ TokenStringTokensopt } StringPostfixopt

TokenStringTokens:
TokenStringToken
TokenStringToken TokenStringTokens

TokenStringToken:
TokenNoBraces
{ TokenStringTokensopt }

Token strings open with the characters q{ and close with the token }. In between must be valid D tokens. The { and } tokens nest. The string is formed of all the characters between the opening and closing of the token string, including comments.

q{this is the voice of} // "this is the voice of"
q{/*}*/ }               // "/*}*/ "
q{ world(q{control}); } // " world(q{control}); "
q{ __TIME__ }           // " __TIME__ "
// i.e. it is not replaced with the time
// q{ __EOF__ }         // error
// __EOF__ is not a token, it's end of file

See also InterpolatedTokenLiteral

Hex Strings

HexString:
x" HexStringCharsopt " StringPostfixopt

HexStringChars:
HexStringChar
HexStringChar HexStringChars

HexStringChar:
HexDigit
WhiteSpace
EndOfLine

Hex strings allow string literals to be created using hex data. The hex data need not form valid UTF characters.

x"0A"              // same as "\x0A"
x"00 FBCD 32FD 0A" // same as "\x00\xFB\xCD\x32\xFD\x0A"

Whitespace and newlines are ignored, so the hex data can be easily formatted. The number of hex characters must be a multiple of 2.

String Postfix

StringPostfix:
c
w
d

The optional StringPostfix character gives a specific type to the string, rather than it being inferred from the context. The types corresponding to the postfix characters are:

String Literal Postfix Characters

Postfix

Type

Alias

c

immutable(char)[]

string

w

immutable(wchar)[]

wstring

d

immutable(dchar)[]

dstring

"hello"c  // string
"hello"w  // wstring
"hello"d  // dstring

The string literals are assembled as UTF-8 char arrays, and the postfix is applied to convert to wchar or dchar as necessary as a final step.

Escape Sequences

EscapeSequence:
\'
\"
\?
\\
\0
\a
\b
\f
\n
\r
\t
\v
\x HexDigit HexDigit
\ OctalDigit
\ OctalDigit OctalDigit
\ OctalDigit OctalDigit OctalDigit
\u HexDigit HexDigit HexDigit HexDigit
\U HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit
\ NamedCharacterEntity

OctalDigit:
0
1
2
3
4
5
6
7

Escape Sequences

Sequence

Meaning

\'

Literal single-quote: '

\"

Literal double-quote: "

\?

Literal question mark: ?

\\

Literal backslash: \

\0

Binary zero (NUL, U+0000).

\a

BEL (alarm) character (U+0007).

\b

Backspace (U+0008).

\f

Form feed (FF) (U+000C).

\n

End-of-line (U+000A).

\r

Carriage return (U+000D).

\t

Horizontal tab (U+0009).

\v

Vertical tab (U+000B).

\xnn

Byte value in hexadecimal, where nn is specified as two hexadecimal digits.
For example: \xFF represents the character with the value 255.
See also: std.conv.hexString.

\n
\nn
\nnn

Byte value in octal.
For example: \101 represents the character with the value 65 ('A'). Analogous to hexadecimal characters, the largest byte value is \377 (= \xFF in hexadecimal or 255 in decimal)
See also: std.conv.octal.

\unnnn

Unicode character U+nnnn, where nnnn are four hexadecimal digits.
For example, \u03B3 represents the Unicode character γ (U+03B3 - GREEK SMALL LETTER GAMMA).

\Unnnnnnnn

Unicode character U+nnnnnnnn, where nnnnnnnn are 8 hexadecimal digits.
For example, \U0001F603 represents the Unicode character U+1F603 (SMILING FACE WITH OPEN MOUTH).

\name

Named character entity from the HTML5 specification.
These names begin with & and end with ;, e.g., &euro;. See NamedCharacterEntity.

Character Literals

CharacterLiteral:
' SingleQuotedCharacter '

SingleQuotedCharacter:
Character
EscapeSequence

Character literals are a single character or escape sequence enclosed by single quotes.

'h'   // the letter h
'\n'  // newline
'\\'  // the backslash character

A character literal resolves to one of type char, wchar, or dchar (see Basic Data Types).

- If the literal is a \u escape sequence, it resolves to type wchar.

- If the literal is a \U escape sequence, it resolves to type dchar.

Otherwise, it resolves to the type with the smallest size it will fit into.

Integer Literals

IntegerLiteral:
Integer
Integer IntegerSuffix

Integer:
DecimalInteger
BinaryInteger
HexadecimalInteger

IntegerSuffix:
L
u
U
Lu
LU
uL
UL

DecimalInteger:
0 Underscoresopt
NonZeroDigit
NonZeroDigit DecimalDigitsUS

Underscores:
_
Underscores _

NonZeroDigit:
1
2
3
4
5
6
7
8
9

DecimalDigits:
DecimalDigit
DecimalDigit DecimalDigits

DecimalDigitsUS:
DecimalDigitUS
DecimalDigitUS DecimalDigitsUS

DecimalDigitsNoSingleUS:
DecimalDigitsUSopt DecimalDigit DecimalDigitsUSopt

DecimalDigitsNoStartingUS:
DecimalDigit
DecimalDigit DecimalDigitsUS

DecimalDigit:
0
NonZeroDigit

DecimalDigitUS:
DecimalDigit
_

BinaryInteger:
BinPrefix BinaryDigitsNoSingleUS

BinPrefix:
0b
0B

BinaryDigitsNoSingleUS:
BinaryDigitsUSopt BinaryDigit BinaryDigitsUSopt

BinaryDigitsUS:
BinaryDigitUS
BinaryDigitUS BinaryDigitsUS

BinaryDigit:
0
1

BinaryDigitUS:
BinaryDigit
_

HexadecimalInteger:
HexPrefix HexDigitsNoSingleUS

HexDigits:
HexDigit
HexDigit HexDigits

HexDigitsUS:
HexDigitUS
HexDigitUS HexDigitsUS

HexDigitsNoSingleUS:
HexDigitsUSopt HexDigit HexDigitsUSopt

HexDigitsNoStartingUS:
HexDigit
HexDigit HexDigitsUS

HexDigit:
DecimalDigit
HexLetter

HexDigitUS:
HexDigit
_

HexLetter:
a
b
c
d
e
f
A
B
C
D
E
F

Integers can be specified in decimal, binary, or hexadecimal.

- Decimal integers are a sequence of decimal digits.

- Binary integers are a sequence of binary digits preceded by a ‘0b’ or ‘0B’.

- C-style octal integer notation (e.g. 0167) was deemed too easy to mix up with decimal notation; it is only fully supported in string literals. D still supports octal integer literals interpreted at compile time through the std.conv.octal template, as in octal!167.

- Hexadecimal integers are a sequence of hexadecimal digits preceded by a ‘0x’ or ‘0X’.

10      // decimal
0b1010  // binary
0xA     // hex

Integers can have embedded ‘_’ characters after a digit to improve readability, which are ignored.

20_000        // leagues under the sea
867_5309      // number on the wall
1_522_000     // thrust of F1 engine (lbf sea level)
0xBAAD_F00D   // magic number for debugging

Integers can be immediately followed by one ‘L’ or one of ‘u’ or ‘U’ or both. Note that there is no ‘l’ suffix.

The type of the integer is resolved as follows:

Decimal Literal Types

Literal

Type

Usual decimal notation

0 .. 2_147_483_647

int

2_147_483_648 .. 9_223_372_036_854_775_807

long

9_223_372_036_854_775_808 .. 18_446_744_073_709_551_615

ulong

Explicit suffixes

0L .. 9_223_372_036_854_775_807L

long

0U .. 4_294_967_295U

uint

4_294_967_296U .. 18_446_744_073_709_551_615U

ulong

0UL .. 18_446_744_073_709_551_615UL

ulong

Hexadecimal notation

0x0 .. 0x7FFF_FFFF

int

0x8000_0000 .. 0xFFFF_FFFF

uint

0x1_0000_0000 .. 0x7FFF_FFFF_FFFF_FFFF

long

0x8000_0000_0000_0000 .. 0xFFFF_FFFF_FFFF_FFFF

ulong

Hexadecimal notation with explicit suffixes

0x0L .. 0x7FFF_FFFF_FFFF_FFFFL

long

0x8000_0000_0000_0000L .. 0xFFFF_FFFF_FFFF_FFFFL

ulong

0x0U .. 0xFFFF_FFFFU

uint

0x1_0000_0000U .. 0xFFFF_FFFF_FFFF_FFFFU

ulong

0x0UL .. 0xFFFF_FFFF_FFFF_FFFFUL

ulong

An integer literal may not exceed these values.

Best Practices: Octal integer notation is not supported for integer literals. However, octal integer literals can be interpreted at compile time through the std.conv.octal template, as in octal!167.

Floating Point Literals

FloatLiteral:
Float Suffixopt
Integer FloatSuffix ImaginarySuffixopt
Integer RealSuffixopt ImaginarySuffix

Float:
DecimalFloat
HexFloat

DecimalFloat:
LeadingDecimal . DecimalDigitsNoStartingUSopt
LeadingDecimal . DecimalDigitsNoStartingUS DecimalExponent
. DecimalDigitsNoStartingUS DecimalExponentopt
LeadingDecimal DecimalExponent

DecimalExponent:
DecimalExponentStart DecimalDigitsNoSingleUS

DecimalExponentStart:
e
E
e+
E+
e-
E-

HexFloat:
HexPrefix HexDigitsNoSingleUS . HexDigitsNoStartingUS HexExponent
HexPrefix . HexDigitsNoStartingUS HexExponent
HexPrefix HexDigitsNoSingleUS HexExponent
HexPrefix HexExponent

HexPrefix:
0x
0X

HexExponent:
HexExponentStart DecimalDigitsNoSingleUS

HexExponentStart:
p
P
p+
P+
p-
P-

Suffix:
FloatSuffix ImaginarySuffixopt
RealSuffix ImaginarySuffixopt
ImaginarySuffix

FloatSuffix:
f
F

RealSuffix:
L

ImaginarySuffix:
i

LeadingDecimal:
DecimalInteger
0 DecimalDigitsNoSingleUS

Floats can be in decimal or hexadecimal format, and must have at least one digit and either a decimal point, an exponent, or a FloatSuffix.

Decimal floats can have an exponent which is e or E followed by a decimal number serving as the exponent of 10.

-1.0
1e2               // 100.0
1e-2              // 0.01
-1.175494351e-38F // float.min

Hexadecimal floats are preceded by a 0x or 0X and the exponent is a p or P followed by a decimal number serving as the exponent of 2.

0xAp0                  // 10.0
0x1p2                  // 4.0
0x1.FFFFFFFFFFFFFp1023 // double.max
0x1p-52                // double.epsilon

Floating literals can have embedded _ characters after a digit to improve readability, which are ignored.

2.645_751
6.022140857E+23
6_022.140857E+20
6_022_.140_857E+20_

- Floating literals with no suffix are of type double.

- Floating literals followed by f or F are of type float.

- Floating literals followed by L are of type real.

0.0                    // double
0F                     // float
0.0L                   // real

The literal may not exceed the range of the type. The literal is rounded to fit into the significant digits of the type.

If a floating literal has a . and a type suffix, at least one digit must be in-between:

1f;  // OK, float
1.f; // error
1.;  // OK, double

Note: Floating literals followed by i to denote imaginary floating point values have been deprecated.

Keywords

Keywords are reserved identifiers.

Keyword:
abstract
alias
align
asm
assert
auto

body
bool
break
byte

case
cast
catch
cdouble
cent
cfloat
char
class
const
continue
creal

dchar
debug
default
delegate
delete
deprecated
do
double

else
enum
export
extern

false
final
finally
float
for
foreach
foreach_reverse
function

goto

idouble
if
ifloat
immutable
import
in
inout
int
interface
invariant
ireal
is

lazy
long

macro
mixin
module

new
nothrow
null

out
override

package
pragma
private
protected
public
pure

real
ref
return

scope
shared
short
static
struct
super
switch
synchronized

template
this
throw
true
try
typeid
typeof

ubyte
ucent
uint
ulong
union
unittest
ushort

version
void

wchar
while
with

__FILE__
__FILE_FULL_PATH__
__FUNCTION__
__LINE__
__MODULE__
__PRETTY_FUNCTION__

__gshared
__parameters
__rvalue
__traits
__vector

Special Tokens

These tokens are replaced with other tokens according to the following table:

Special Tokens

Special Token

Replaced with

__DATE__

string literal of the date of compilation "mmm dd yyyy"

__EOF__

tells the scanner to ignore everything after this token

__TIME__

string literal of the time of compilation "hh:mm:ss"

__TIMESTAMP__

string literal of the date and time of compilation "www mmm dd hh:mm:ss yyyy"

__VENDOR__

Compiler vendor string

__VERSION__

Compiler version as an integer

Implementation Defined: The replacement string literal for __VENDOR__ and the replacement integer value for __VERSION__.

Special Token Sequences

SpecialTokenSequence:
# line IntegerLiteral Filespecopt EndOfLine
# line __LINE__ Filespecopt EndOfLine

Filespec:
" DoubleQuotedCharactersopt "

Special token sequences are processed by the lexical analyzer, may appear between any other tokens, and do not affect the syntax parsing.

Special token sequences are terminated by the first newline that follows the first # token at the beginning of the sequence.

There is currently only one special token sequence, #line.

This sets the line number of the next source line to IntegerLiteral, and optionally the current source file name to Filespec, beginning with the next line of source text.

For example:

int #line 6 "pkg/mod.d"
x;  // this is now line 6 of file pkg/mod.d

Implementation Defined: The source file and line number is typically used for printing error messages and for mapping generated code back to the source for the symbolic debugging output.

Interpolation Expression Sequence

Interpolation Expression Sequences (IES) are expressions that intersperse string literal data and values. An interpolation expression is written like a string, but can contain values that are passed directly to a function that is able to accept them. It is transformed into a Sequence of expressions that can be overloaded or handled by template functions.

IES Literals

InterpolationExpressionSequence:
InterpolatedDoubleQuotedLiteral
InterpolatedWysiwygLiteral
InterpolatedTokenLiteral

An Interpolation Expression sequence can be either wysiwyg quoted, double quoted, or a token literal. Only double quoted literals can have escapes in them.

Unlike string literals, IES literals cannot have a suffix defining the width of the character type for the string expressions.

Double Quoted IES Literals

InterpolatedDoubleQuotedLiteral:
i" InterpolatedDoubleQuotedCharactersopt "

InterpolatedDoubleQuotedCharacters:
InterpolatedDoubleQuotedCharacter
InterpolatedDoubleQuotedCharacter InterpolatedDoubleQuotedCharacters

InterpolatedDoubleQuotedCharacter:
DoubleQuotedCharacter
InterpolationEscapeSequence
InterpolationExpression

InterpolationEscapeSequence:
EscapeSequence
\$

InterpolationExpression:
$( AssignExpression )

Like DoubleQuotedString, double-quoted IES literals can have escape characters in them. Added to the normal escapes is the ability to escape a literal $

A $ followed by any character other than a left parenthesis is treated as a literal $ in the expression, there is no need to escape it.

The expression inside an InterpolationExpression is a full D expression, and escapes are not needed inside that part of the expression.

Wysiwyg IES Literals

InterpolatedWysiwygLiteral:
i` InterpolatedWysiwygCharactersopt `

InterpolatedWysiwygCharacters:
InterpolatedWysiwygCharacter
InterpolatedWysiwygCharacter InterpolatedWysiwygCharacters

InterpolatedWysiwygCharacter:
WysiwygCharacter
InterpolationExpression

Wysiwyg ("what you see is what you get") IES literals are defined like WysiwygString strings, but only support backquote syntax. No escapes are recognized inside these literals.

Token IES Literals

InterpolatedTokenLiteral:
iq{ InterpolatedTokenStringTokensopt }

InterpolatedTokenStringTokens:
InterpolatedTokenStringToken
InterpolatedTokenStringToken InterpolatedTokenStringTokens

InterpolatedTokenStringToken:
InterpolatedTokenNoBraces
{ InterpolatedTokenStringTokensopt }

InterpolatedTokenNoBraces:
TokenNoBraces
InterpolationExpression

Like TokenString, IES Token literals must contain only valid D tokens, with the exception of InterpolationExpression. No escapes are recognized.

Expression Translation

When the lexer encounters an Interpolation Expression Sequence, the token is translated into a sequence of expressions, which replaces the single token. The sequence always starts with the expression core.interpolation.InterpolationHeader() and always ends with core.interpolation.InterpolationFooter()

Each part str of the token which is literal string data is translated into the expression core.interpolation.InterpolatedLiteral!(str)

Each part $(expr) of the token which is an InterpolationExpression is translated into the sequence core.interpolation.InterpolatedExpression!(expr), expr.

// simple version of std.typecons.Tuple
struct Tuple(T...) { T value; }
Tuple!T tuple(T...)(T value) { return Tuple!T(value); }

import core.interpolation;
string name = "John Doe";
auto items = tuple(i"Hello, $(name), how are you?");
assert(items == tuple(
InterpolationHeader(),                       // denotes the start of an IES
InterpolatedLiteral!("Hello, ")(),           // literal string data
InterpolatedExpression!("name")(),           // expression literal data
name,                                        // expression passed directly
InterpolatedLiteral!(", how are you?")(),    // literal string data
InterpolationFooter()));                     // denotes the end of an IES

core.interpolation Types

Types defined in core.interpolation need not be imported to use IES. These are automatically imported when an IES is used. The types are defined so as to make it easy to introspect the IES for processing at compile-time.

InterpolationHeader and InterpolationFooter

The InterpolationHeader and InterpolationFooter type are empty structs that allow easy overloading of functions to handle IES. They also can be used to understand which parts of a expression list were passed via IES.

These types have a toString definition that returns an empty string, to allow for processing by functions which intend to convert IES to text, such as std.stdio.writeln or std.conv.text.

InterpolatedLiteral

The InterpolatedLiteral type is an empty struct that provides compile-time access to the string literal portion of an IES. This type also provides a toString member function which returns the part of the sequence that this value replaced.

InterpolatedExpression

The InterpolatedExpression type is an empty struct that provides compile-time access to the literal that was used to form the following expression. It provides a toString member function which returns the empty string. It also has an enum expression member, which is equal to the template parameter used to construct the type.

string name = "John Doe";
auto ies = i"Hello, $(name)";
static assert(is(typeof(ies[0]) == InterpolationHeader));
static assert(ies[1].toString() == "Hello, ");
static assert(ies[2].expression == "name");
assert(ies[3] == name);
static assert(is(typeof(ies[4]) == InterpolationFooter));

Accepting and Processing IES

The recommended mechanism to accept IES is to provide a variadic template function to match the various parameters inside the sequence, surrounded by explicit InterpolationHeader and InterpolationFooter parameters.

void processIES(Sequence...)(InterpolationHeader, Sequence data, InterpolationFooter)
{
// process `data` here
}

An IES can also contain types as interpolation expressions. This can be used by passing to a variadic template parameter.

template processIESAtCompileTime(InterpolationHeader header, Sequence...)
{
static assert(Sequence[$-1] == InterpolationFooter());
}

alias result = processIES!i"Here is a type: $(int)";

Converting to a String

In many cases, it is desirable to convert an IES to a string. The Phobos function std.conv.text can convert the IES to a string for use in any context where a string is needed, for instance to assign to a string variable, or call a function that accepts a string.

import std.conv : text;
string name = "John Doe";

string badgreeting = i"Hello, $(name)"; // Error
string greeting = i"Hello, $(name)".text; // OK
assert(greeting == "Hello, John Doe");

It is highly recommended for library authors who wish to accept IES to provide an overload that accepts them, rather than rely on std.conv, as this incurs unnecessary allocations. This is especially important where certain types of injection attacks are possible from malicious user-provided data.

Grammar

After the lexical analyzer has created tokens from the source text, the tokens are parsed into the following key constructs:

- A Module normally has DeclDefs.

- DeclDef can be a Declaration.

- Declaration includes VarDeclarations, FuncDeclaration and AggregateDeclaration (e.g. class and struct declarations).

Note: Many nodes are omitted from these diagrams.

- VarDeclarations declares one or more variables with a Type.

- Each concrete AggregateDeclaration has DeclDef members.

- FuncDeclaration can have a BlockStatement.

- BlockStatement can have Statements.

- Statement includes BlockStatement, DeclarationStatement, ExpressionStatement and control-flow statements.

- DeclarationStatement has a Declaration.

- ExpressionStatement has an Expression.

- Expression includes PrimaryExpression, AssignExpression and PostfixExpression (e.g. for a function call).

- PrimaryExpression includes Identifier and LiteralExpression.

- AssignExpression and PostfixExpression each have at least one kind of Expression.

- Every Expression has a Type.

Note: The remainder of this page is auto-generated, see the other pages for semantic details.

Modules

Module:
ModuleDeclaration
ModuleDeclaration DeclDefs
DeclDefs

DeclDefs:
DeclDef
DeclDef DeclDefs

DeclDef:
AttributeSpecifier
Declaration
Constructor
Destructor
Postblit
Invariant
UnitTest
AliasThis
StaticConstructor
StaticDestructor
SharedStaticConstructor
SharedStaticDestructor
ConditionalDeclaration
DebugSpecification
VersionSpecification
MixinDeclaration
EmptyDeclaration

EmptyDeclaration:
;

ModuleDeclaration:
ModuleAttributesopt module ModuleFullyQualifiedName ;

ModuleAttributes:
ModuleAttribute
ModuleAttribute ModuleAttributes

ModuleAttribute:
DeprecatedAttribute
UserDefinedAttribute

ModuleFullyQualifiedName:
ModuleName
Packages . ModuleName

ModuleName:
Identifier

Packages:
PackageName
Packages . PackageName

PackageName:
Identifier

ImportDeclaration:
import ImportList ;
static import ImportList ;

ImportList:
Import
ImportBindings
Import , ImportList

Import:
ModuleFullyQualifiedName
ModuleAliasIdentifier = ModuleFullyQualifiedName

ImportBindings:
Import : ImportBindList

ImportBindList:
ImportBind
ImportBind , ImportBindList

ImportBind:
Identifier
Identifier = Identifier

ModuleAliasIdentifier:
Identifier

MixinDeclaration:
mixin ( ArgumentList ) ;

Declarations

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

AggregateDeclaration:
ClassDeclaration
InterfaceDeclaration
StructDeclaration
UnionDeclaration

VarDeclarations:
StorageClassesopt BasicType TypeSuffixesopt IdentifierInitializers ;
AutoDeclaration

IdentifierInitializers:
IdentifierInitializer
IdentifierInitializer , IdentifierInitializers

IdentifierInitializer:
Identifier
Identifier TemplateParametersopt = Initializer

Declarator:
TypeSuffixesopt Identifier

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

Initializer:
VoidInitializer
NonVoidInitializer

NonVoidInitializer:
ArrayInitializer
StructInitializer
AssignExpression

VoidInitializer:
void

AutoDeclaration:
StorageClasses AutoAssignments ;

AutoAssignments:
AutoAssignment
AutoAssignments , AutoAssignment

AutoAssignment:
Identifier TemplateParametersopt = Initializer

AliasDeclaration:
alias StorageClassesopt BasicType TypeSuffixesopt Identifiers ;
alias StorageClassesopt BasicType FuncDeclarator ;
alias AliasAssignments ;

Identifiers:
Identifier
Identifier , Identifiers

AliasAssignments:
AliasAssignment
AliasAssignments , AliasAssignment

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

AliasAssign:
Identifier = Type

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

Types

Type:
TypeCtorsopt BasicType TypeSuffixesopt

TypeCtors:
TypeCtor
TypeCtor TypeCtors

TypeCtor:
const
immutable
inout
shared

BasicType:
FundamentalType
. QualifiedIdentifier
QualifiedIdentifier
Typeof
Typeof . QualifiedIdentifier
TypeCtor ( Type )
Vector
TraitsExpression
MixinType

Vector:
__vector ( VectorBaseType )

VectorBaseType:
Type

FundamentalType:
bool
byte
ubyte
short
ushort
int
uint
long
ulong
cent
ucent
char
wchar
dchar
float
double
real
ifloat
idouble
ireal
cfloat
cdouble
creal
void

TypeSuffixes:
TypeSuffix TypeSuffixesopt

TypeSuffix:
*
[ ]
[ AssignExpression ]
[ AssignExpression .. AssignExpression ]
[ Type ]
delegate Parameters MemberFunctionAttributesopt
function Parameters FunctionAttributesopt

QualifiedIdentifier:
Identifier
Identifier . QualifiedIdentifier
TemplateInstance
TemplateInstance . QualifiedIdentifier
Identifier [ AssignExpression ]
Identifier [ AssignExpression ] . QualifiedIdentifier

Typeof:
typeof ( Expression )
typeof ( return )

MixinType:
mixin ( ArgumentList )

Attributes

AttributeSpecifier:
Attribute :
Attribute DeclarationBlock

Attribute:
AlignAttribute
AtAttribute
DeprecatedAttribute
FunctionAttributeKwd
LinkageAttribute
Pragma
VisibilityAttribute
abstract
auto
const
final
__gshared
extern
immutable
inout
override
ref
return
__rvalue
scope
shared
static
synchronized

FunctionAttributeKwd:
nothrow
pure

AtAttribute:
@ disable
@ __future
@ nogc
@ live
Property
@ safe
@ system
@ trusted
UserDefinedAttribute

Property:
@ property

DeclarationBlock:
DeclDef
{ DeclDefsopt }

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

AlignAttribute:
align ( default )
align
align ( AssignExpression )

DeprecatedAttribute:
deprecated
deprecated ( AssignExpression )

VisibilityAttribute:
export
package
package ( QualifiedIdentifier )
private
protected
public

UserDefinedAttribute:
@ ( TemplateArgumentList )
@ TemplateSingleArgument
@ Identifier ( NamedArgumentListopt )
@ TemplateInstance
@ TemplateInstance ( NamedArgumentListopt )

Pragmas

PragmaDeclaration:
Pragma ;
Pragma DeclarationBlock

PragmaStatement:
Pragma ;
Pragma NoScopeStatement

Pragma:
pragma ( Identifier )
pragma ( Identifier , ArgumentList )

Expressions

Expression:
CommaExpression

CommaExpression:
AssignExpression
CommaExpression , AssignExpression

AssignExpression:
ConditionalExpression
ConditionalExpression = AssignExpression
ConditionalExpression += AssignExpression
ConditionalExpression -= AssignExpression
ConditionalExpression *= AssignExpression
ConditionalExpression /= AssignExpression
ConditionalExpression %= AssignExpression
ConditionalExpression &= AssignExpression
ConditionalExpression |= AssignExpression
ConditionalExpression ^= AssignExpression
ConditionalExpression ~= AssignExpression
ConditionalExpression <<= AssignExpression
ConditionalExpression >>= AssignExpression
ConditionalExpression >>>= AssignExpression
ConditionalExpression ^^= AssignExpression

ConditionalExpression:
OrOrExpression
OrOrExpression ? Expression : ConditionalExpression

OrOrExpression:
AndAndExpression
OrOrExpression || AndAndExpression

AndAndExpression:
OrExpression
AndAndExpression && OrExpression

OrExpression:
XorExpression
OrExpression | XorExpression

XorExpression:
AndExpression
XorExpression ^ AndExpression

AndExpression:
CmpExpression
AndExpression & CmpExpression

CmpExpression:
EqualExpression
IdentityExpression
RelExpression
InExpression
ShiftExpression

EqualExpression:
ShiftExpression == ShiftExpression
ShiftExpression != ShiftExpression

IdentityExpression:
ShiftExpression is ShiftExpression
ShiftExpression ! is ShiftExpression

RelExpression:
ShiftExpression < ShiftExpression
ShiftExpression <= ShiftExpression
ShiftExpression > ShiftExpression
ShiftExpression >= ShiftExpression

InExpression:
ShiftExpression in ShiftExpression
ShiftExpression ! in ShiftExpression

ShiftExpression:
AddExpression
ShiftExpression << AddExpression
ShiftExpression >> AddExpression
ShiftExpression >>> AddExpression

AddExpression:
MulExpression
AddExpression + MulExpression
AddExpression - MulExpression
AddExpression ~ MulExpression

MulExpression:
UnaryExpression
MulExpression * UnaryExpression
MulExpression / UnaryExpression
MulExpression % UnaryExpression

UnaryExpression:
& UnaryExpression
++ UnaryExpression
-- UnaryExpression
* UnaryExpression
- UnaryExpression
+ UnaryExpression
! UnaryExpression
ComplementExpression
DeleteExpression
CastExpression
ThrowExpression
PowExpression

ComplementExpression:
~ UnaryExpression

DeleteExpression:
delete UnaryExpression

CastExpression:
cast ( Type ) UnaryExpression
CastQual

CastQual:
cast ( TypeCtorsopt ) UnaryExpression

ThrowExpression:
throw AssignExpression

PowExpression:
PostfixExpression
PostfixExpression ^^ UnaryExpression

PostfixExpression:
PrimaryExpression
PostfixExpression . Identifier
PostfixExpression . TemplateInstance
PostfixExpression . NewExpression
PostfixExpression ++
PostfixExpression --
PostfixExpression ( NamedArgumentListopt )
TypeCtorsopt BasicType ( NamedArgumentListopt )
PostfixExpression IndexOperation
PostfixExpression SliceOperation

ArgumentList:
AssignExpression
AssignExpression ,
AssignExpression , ArgumentList

NamedArgumentList:
NamedArgument
NamedArgument ,
NamedArgument , NamedArgumentList

NamedArgument:
Identifier : AssignExpression
AssignExpression

IndexOperation:
[ ArgumentList ]

SliceOperation:
[ ]
[ Slice ]
[ Slice , ]

Slice:
AssignExpression
AssignExpression , Slice
AssignExpression .. AssignExpression
AssignExpression .. AssignExpression , Slice

PrimaryExpression:
Identifier
. Identifier
TemplateInstance
. TemplateInstance
$
LiteralExpression
AssertExpression
MixinExpression
ImportExpression
NewExpression
FundamentalType . Identifier
TypeCtoropt ( Type ) . Identifier
( Type ) . TemplateInstance
FundamentalType ( NamedArgumentListopt )
TypeCtoropt ( Type ) ( NamedArgumentListopt )
Typeof
TypeidExpression
IsExpression
( Expression )
SpecialKeyword
RvalueExpression
TraitsExpression

LiteralExpression:
this
super
null
true
false
IntegerLiteral
FloatLiteral
CharacterLiteral
StringLiteral
InterpolationExpressionSequence
ArrayLiteral
AssocArrayLiteral
FunctionLiteral

ArrayLiteral:
[ ArgumentListopt ]

AssocArrayLiteral:
[ KeyValuePairs ]

KeyValuePairs:
KeyValuePair
KeyValuePair , KeyValuePairs

KeyValuePair:
KeyExpression : ValueExpression

KeyExpression:
AssignExpression

ValueExpression:
AssignExpression

FunctionLiteral:
function RefOrAutoRefopt BasicTypeWithSuffixesopt ParameterWithAttributesopt FunctionLiteralBody
delegate RefOrAutoRefopt BasicTypeWithSuffixesopt ParameterWithMemberAttributesopt FunctionLiteralBody
RefOrAutoRefopt ParameterWithMemberAttributes FunctionLiteralBody
BlockStatement
Identifier => AssignExpression

BasicTypeWithSuffixes:
BasicType TypeSuffixesopt

ParameterWithAttributes:
Parameters FunctionAttributesopt

ParameterWithMemberAttributes:
Parameters MemberFunctionAttributesopt

FunctionLiteralBody:
=> AssignExpression
SpecifiedFunctionBody

RefOrAutoRef:
ref
auto ref

AssertExpression:
assert ( AssertArguments )

AssertArguments:
AssignExpression
AssignExpression ,
AssignExpression , AssignExpression
AssignExpression , AssignExpression ,

MixinExpression:
mixin ( ArgumentList )

ImportExpression:
import ( AssignExpression )

NewExpression:
new Type
new Type [ AssignExpression ]
new Type ( NamedArgumentListopt )
NewAnonClassExpression

TypeidExpression:
typeid ( Type )
typeid ( Expression )

IsExpression:
is ( Type )
is ( Type : TypeSpecialization )
is ( Type == TypeSpecialization )
is ( Type : TypeSpecialization , TemplateParameterList )
is ( Type == TypeSpecialization , TemplateParameterList )
is ( Type Identifier )
is ( Type Identifier : TypeSpecialization )
is ( Type Identifier == TypeSpecialization )
is ( Type Identifier : TypeSpecialization , TemplateParameterList )
is ( Type Identifier == TypeSpecialization , TemplateParameterList )

TypeSpecialization:
Type
TypeCtor
struct
union
class
interface
enum
__vector
function
delegate
super
return
__parameters
module
package

RvalueExpression:
__rvalue ( AssignExpression )

SpecialKeyword:
__FILE__
__FILE_FULL_PATH__
__MODULE__
__LINE__
__FUNCTION__
__PRETTY_FUNCTION__

Statements

Statement:
EmptyStatement
NonEmptyStatement
ScopeBlockStatement

EmptyStatement:
;

NoScopeNonEmptyStatement:
NonEmptyStatement
BlockStatement

NoScopeStatement:
EmptyStatement
NonEmptyStatement
BlockStatement

NonEmptyOrScopeBlockStatement:
NonEmptyStatement
ScopeBlockStatement

NonEmptyStatement:
NonEmptyStatementNoCaseNoDefault
CaseStatement
CaseRangeStatement
DefaultStatement

NonEmptyStatementNoCaseNoDefault:
LabeledStatement
ExpressionStatement
DeclarationStatement
IfStatement
WhileStatement
DoStatement
ForStatement
ForeachStatement
SwitchStatement
FinalSwitchStatement
ContinueStatement
BreakStatement
ReturnStatement
GotoStatement
WithStatement
SynchronizedStatement
TryStatement
ScopeGuardStatement
AsmStatement
MixinStatement
ForeachRangeStatement
PragmaStatement
ConditionalStatement
StaticForeachStatement
ImportDeclaration

ScopeStatement:
NonEmptyStatement
BlockStatement

ScopeBlockStatement:
BlockStatement

LabeledStatement:
Identifier :
Identifier : Statement

BlockStatement:
{ }
{ StatementList }

StatementList:
Statement
Statement StatementList

ExpressionStatement:
Expression ;

DeclarationStatement:
StorageClassesopt Declaration

IfStatement:
if ( IfCondition ) ThenStatement
if ( IfCondition ) ThenStatement else ElseStatement

IfCondition:
Expression
IfConditionStorageClasses Identifier = Expression
IfConditionStorageClassesopt BasicType Declarator = Expression

IfConditionStorageClasses:
IfConditionStorageClass
IfConditionStorageClass IfConditionStorageClasses

IfConditionStorageClass:
scope
auto
ref
TypeCtor

ThenStatement:
ScopeStatement

ElseStatement:
ScopeStatement

WhileStatement:
while ( IfCondition ) ScopeStatement

DoStatement:
do ScopeStatement  while ( Expression ) ;

ForStatement:
for ( Initialize Testopt ; Incrementopt ) ScopeStatement

Initialize:
;
NoScopeNonEmptyStatement

Test:
Expression

Increment:
Expression

AggregateForeach:
Foreach ( ForeachTypeList ; ForeachAggregate )

ForeachStatement:
AggregateForeach NoScopeNonEmptyStatement

Foreach:
foreach
foreach_reverse

ForeachTypeList:
ForeachType
ForeachType , ForeachTypeList

ForeachType:
ForeachTypeAttributesopt BasicType Declarator
ForeachTypeAttributesopt Identifier
ForeachTypeAttributesopt alias Identifier

ForeachTypeAttributes:
ForeachTypeAttribute
ForeachTypeAttribute ForeachTypeAttributes

ForeachTypeAttribute:
enum
ref
scope
TypeCtor

ForeachAggregate:
Expression

RangeForeach:
Foreach ( ForeachType ; LwrExpression .. UprExpression )

LwrExpression:
Expression

UprExpression:
Expression

ForeachRangeStatement:
RangeForeach ScopeStatement

SwitchStatement:
switch ( IfCondition ) ScopeStatement

CaseStatement:
case ArgumentList : ScopeStatementListopt

DefaultStatement:
default : ScopeStatementListopt

ScopeStatementList:
StatementListNoCaseNoDefault

StatementListNoCaseNoDefault:
StatementNoCaseNoDefault
StatementNoCaseNoDefault StatementListNoCaseNoDefault

StatementNoCaseNoDefault:
EmptyStatement
NonEmptyStatementNoCaseNoDefault
ScopeBlockStatement

CaseRangeStatement:
case FirstExp : .. case LastExp : ScopeStatementListopt

FirstExp:
AssignExpression

LastExp:
AssignExpression

FinalSwitchStatement:
final switch ( IfCondition ) ScopeStatement

ContinueStatement:
continue Identifieropt ;

BreakStatement:
break Identifieropt ;

ReturnStatement:
return Expressionopt ;

GotoStatement:
goto Identifier ;
goto default ;
goto case ;
goto case Expression ;

WithStatement:
with ( Expression ) ScopeStatement
with ( Symbol ) ScopeStatement
with ( TemplateInstance ) ScopeStatement

SynchronizedStatement:
synchronized ScopeStatement
synchronized ( Expression ) ScopeStatement

TryStatement:
try ScopeStatement Catches
try ScopeStatement Catches FinallyStatement
try ScopeStatement FinallyStatement

Catches:
Catch
Catch Catches

Catch:
catch ( CatchParameter ) NoScopeNonEmptyStatement

CatchParameter:
BasicType Identifieropt

FinallyStatement:
finally NoScopeNonEmptyStatement

ScopeGuardStatement:
scope ( exit ) NonEmptyOrScopeBlockStatement
scope ( success ) NonEmptyOrScopeBlockStatement
scope ( failure ) NonEmptyOrScopeBlockStatement

AsmStatement:
asm FunctionAttributesopt { AsmInstructionListopt }

AsmInstructionList:
AsmInstruction ;
AsmInstruction ; AsmInstructionList

MixinStatement:
mixin ( ArgumentList ) ;

Arrays

ArrayInitializer:
[ ArrayElementInitializersopt ]

ArrayElementInitializers:
ArrayElementInitializer
ArrayElementInitializer ,
ArrayElementInitializer , ArrayElementInitializers

ArrayElementInitializer:
NonVoidInitializer
AssignExpression : NonVoidInitializer

Structs and Unions

StructDeclaration:
struct Identifier ;
struct Identifier AggregateBody
StructTemplateDeclaration
AnonStructDeclaration

AnonStructDeclaration:
struct AggregateBody

UnionDeclaration:
union Identifier ;
union Identifier AggregateBody
UnionTemplateDeclaration
AnonUnionDeclaration

AnonUnionDeclaration:
union AggregateBody

AggregateBody:
{ DeclDefsopt }

StructInitializer:
{ StructMemberInitializersopt }

StructMemberInitializers:
StructMemberInitializer
StructMemberInitializer ,
StructMemberInitializer , StructMemberInitializers

StructMemberInitializer:
NonVoidInitializer
Identifier : NonVoidInitializer

Postblit:
this ( this ) MemberFunctionAttributesopt FunctionBody
this ( this ) MemberFunctionAttributesopt MissingFunctionBody

Invariant:
invariant ( ) BlockStatement
invariant BlockStatement
invariant ( AssertArguments ) ;

AliasThis:
alias Identifier this ;
alias this = Identifier ;

Classes

ClassDeclaration:
class Identifier ;
class Identifier BaseClassListopt AggregateBody
ClassTemplateDeclaration

BaseClassList:
: SuperClassOrInterface
: SuperClassOrInterface , Interfaces

SuperClassOrInterface:
BasicType

Interfaces:
Interface
Interface , Interfaces

Interface:
BasicType

Constructor:
this Parameters MemberFunctionAttributesopt FunctionBody
this Parameters MemberFunctionAttributesopt MissingFunctionBody
ConstructorTemplate

Destructor:
~ this ( ) MemberFunctionAttributesopt FunctionBody
~ this ( ) MemberFunctionAttributesopt MissingFunctionBody

StaticConstructor:
static this ( ) MemberFunctionAttributesopt FunctionBody
static this ( ) MemberFunctionAttributesopt MissingFunctionBody

StaticDestructor:
static ~ this ( ) MemberFunctionAttributesopt FunctionBody

SharedStaticConstructor:
shared static this ( ) MemberFunctionAttributesopt FunctionBody
shared static this ( ) MemberFunctionAttributesopt MissingFunctionBody

SharedStaticDestructor:
shared static ~ this ( ) MemberFunctionAttributesopt FunctionBody
shared static ~ this ( ) MemberFunctionAttributesopt MissingFunctionBody

Invariant:
invariant ( ) BlockStatement
invariant BlockStatement
invariant ( AssertArguments ) ;

NewAnonClassExpression:
new class ConstructorArgsopt AnonBaseClassListopt AggregateBody

ConstructorArgs:
( NamedArgumentListopt )

AnonBaseClassList:
SuperClassOrInterface
SuperClassOrInterface , Interfaces

Interfaces

InterfaceDeclaration:
interface Identifier ;
interface Identifier BaseInterfaceListopt AggregateBody
InterfaceTemplateDeclaration

BaseInterfaceList:
: Interfaces

Enums

EnumDeclaration:
enum Identifier EnumBody
enum Identifier : EnumBaseType EnumBody
AnonymousEnumDeclaration

EnumBaseType:
Type

EnumBody:
{ EnumMembers }
;

EnumMembers:
EnumMember
EnumMember ,
EnumMember , EnumMembers

EnumMember:
EnumMemberAttributesopt Identifier
EnumMemberAttributesopt Identifier = AssignExpression

EnumMemberAttributes:
EnumMemberAttribute
EnumMemberAttribute EnumMemberAttributes

EnumMemberAttribute:
DeprecatedAttribute
UserDefinedAttribute
@ disable

AnonymousEnumDeclaration:
enum : EnumBaseType { EnumMembers }
enum { AnonymousEnumMembers }

AnonymousEnumMembers:
AnonymousEnumMember
AnonymousEnumMember ,
AnonymousEnumMember , AnonymousEnumMembers

AnonymousEnumMember:
EnumMember
EnumMemberAttributesopt Type Identifier = AssignExpression

Functions

FuncDeclaration:
StorageClassesopt BasicType FuncDeclarator FunctionBody
StorageClassesopt BasicType FuncDeclarator MissingFunctionBody
AutoFuncDeclaration

AutoFuncDeclaration:
StorageClasses Identifier FuncDeclaratorSuffix FunctionBody

FuncDeclarator:
TypeSuffixesopt Identifier FuncDeclaratorSuffix

FuncDeclaratorSuffix:
Parameters MemberFunctionAttributesopt
TemplateParameters Parameters MemberFunctionAttributesopt Constraintopt

Parameters:
( ParameterListopt )

ParameterList:
Parameter
Parameter , ParameterListopt
VariadicArgumentsAttributesopt ...

Parameter:
ParameterDeclaration
ParameterDeclaration ...
ParameterDeclaration = AssignExpression
ParameterDeclaration = AssignExpression ...

ParameterDeclaration:
ParameterAttributesopt BasicType Declarator
ParameterAttributesopt Type

ParameterAttributes:
ParameterStorageClass
UserDefinedAttribute
ParameterAttributes ParameterStorageClass
ParameterAttributes UserDefinedAttribute

ParameterStorageClass:
auto
TypeCtor
final
in
lazy
out
ref
return
scope

VariadicArgumentsAttributes:
VariadicArgumentsAttribute
VariadicArgumentsAttribute VariadicArgumentsAttributes

VariadicArgumentsAttribute:
const
immutable
return
scope
shared

FunctionAttributes:
FunctionAttribute
FunctionAttribute FunctionAttributes

FunctionAttribute:
FunctionAttributeKwd
Property
AtAttribute

MemberFunctionAttributes:
MemberFunctionAttribute
MemberFunctionAttribute MemberFunctionAttributes

MemberFunctionAttribute:
const
immutable
inout
return
scope
shared
FunctionAttribute

FunctionBody:
SpecifiedFunctionBody
ShortenedFunctionBody

SpecifiedFunctionBody:
doopt BlockStatement
FunctionContractsopt InOutContractExpression doopt BlockStatement
FunctionContractsopt InOutStatement do BlockStatement

ShortenedFunctionBody:
InOutContractExpressionsopt => AssignExpression ;

MissingFunctionBody:
;
FunctionContractsopt InOutContractExpression ;
FunctionContractsopt InOutStatement

FunctionContracts:
FunctionContract
FunctionContract FunctionContracts

FunctionContract:
InOutContractExpression
InOutStatement

InOutContractExpressions:
InOutContractExpression
InOutContractExpression InOutContractExpressions

InOutContractExpression:
InContractExpression
OutContractExpression

InOutStatement:
InStatement
OutStatement

InContractExpression:
in ( AssertArguments )

InStatement:
in BlockStatement

OutContractExpression:
out ( ; AssertArguments )
out ( Identifier ; AssertArguments )

OutStatement:
out BlockStatement
out ( Identifier ) BlockStatement

MainFunction:
MainReturnDecl main() FunctionBody
MainReturnDecl main(string[] Identifier) FunctionBody

MainReturnDecl:
void
int
noreturn
auto

CMainFunction:
extern (C) MainReturnDecl main(CmainParametersopt) BlockStatement

CmainParameters:
int Identifier, char** Identifier
int Identifier, char** Identifier, char** Identifier

Templates

TemplateDeclaration:
template Identifier TemplateParameters Constraintopt { DeclDefsopt }

TemplateParameters:
( TemplateParameterListopt )

TemplateParameterList:
TemplateParameter
TemplateParameter ,
TemplateParameter , TemplateParameterList

TemplateInstance:
Identifier TemplateArguments

TemplateArguments:
! ( TemplateArgumentListopt )
! TemplateSingleArgument

TemplateArgumentList:
TemplateArgument
TemplateArgument ,
TemplateArgument , TemplateArgumentList

TemplateSingleArgument:
Identifier
FundamentalType
CharacterLiteral
StringLiteral
InterpolationExpressionSequence
IntegerLiteral
FloatLiteral
true
false
null
this
SpecialKeyword

TemplateArgument:
Type
AssignExpression
Symbol

Symbol:
SymbolTail
. SymbolTail

SymbolTail:
Identifier
Identifier . SymbolTail
TemplateInstance
TemplateInstance . SymbolTail

TemplateParameter:
TemplateTypeParameter
TemplateValueParameter
TemplateAliasParameter
TemplateSequenceParameter
TemplateThisParameter

TemplateTypeParameter:
Identifier TemplateTypeParameterSpecializationopt TemplateTypeParameterDefaultopt

TemplateTypeParameterSpecialization:
: Type

TemplateTypeParameterDefault:
= Type

TemplateThisParameter:
this TemplateTypeParameter

TemplateValueParameter:
BasicType Declarator TemplateValueParameterSpecializationopt TemplateValueParameterDefaultopt

TemplateValueParameterSpecialization:
: ConditionalExpression

TemplateValueParameterDefault:
= AssignExpression
= SpecialKeyword

TemplateAliasParameter:
alias Identifier TemplateAliasParameterSpecializationopt TemplateAliasParameterDefaultopt
alias BasicType Declarator TemplateAliasParameterSpecializationopt TemplateAliasParameterDefaultopt

TemplateAliasParameterSpecialization:
: Type
: ConditionalExpression

TemplateAliasParameterDefault:
= Type
= ConditionalExpression

TemplateSequenceParameter:
Identifier ...

ClassTemplateDeclaration:
class Identifier TemplateParameters ;
class Identifier TemplateParameters Constraintopt BaseClassListopt AggregateBody
class Identifier TemplateParameters BaseClassListopt Constraintopt AggregateBody

InterfaceTemplateDeclaration:
interface Identifier TemplateParameters ;
interface Identifier TemplateParameters Constraintopt BaseInterfaceListopt AggregateBody
interface Identifier TemplateParameters BaseInterfaceList Constraint AggregateBody

StructTemplateDeclaration:
struct Identifier TemplateParameters ;
struct Identifier TemplateParameters Constraintopt AggregateBody

UnionTemplateDeclaration:
union Identifier TemplateParameters ;
union Identifier TemplateParameters Constraintopt AggregateBody

ConstructorTemplate:
this TemplateParameters Parameters MemberFunctionAttributesopt Constraintopt FunctionBody
this TemplateParameters Parameters MemberFunctionAttributesopt Constraintopt MissingFunctionBody

Constraint:
if ( Expression )

Template Mixins

TemplateMixinDeclaration:
mixin template Identifier TemplateParameters Constraintopt { DeclDefsopt }

TemplateMixin:
mixin MixinTemplateName TemplateArgumentsopt Identifieropt ;
mixin Identifier = MixinTemplateName TemplateArgumentsopt ;

MixinTemplateName:
. MixinQualifiedIdentifier
MixinQualifiedIdentifier
Typeof . MixinQualifiedIdentifier

MixinQualifiedIdentifier:
Identifier
Identifier . MixinQualifiedIdentifier
TemplateInstance . MixinQualifiedIdentifier

Conditional Compilation

ConditionalDeclaration:
Condition DeclarationBlock
Condition DeclarationBlock else DeclarationBlock
Condition : DeclDefsopt
Condition DeclarationBlock else : DeclDefsopt

ConditionalStatement:
Condition NoScopeNonEmptyStatement
Condition NoScopeNonEmptyStatement else NoScopeNonEmptyStatement

Condition:
VersionCondition
DebugCondition
StaticIfCondition

VersionCondition:
version ( Identifier )
version ( unittest )
version ( assert )

VersionSpecification:
version = Identifier ;

DebugCondition:
debug
debug ( Identifier )

DebugSpecification:
debug = Identifier ;

StaticIfCondition:
static if ( AssignExpression )

StaticForeachDeclaration:
StaticForeach DeclarationBlock
StaticForeach : DeclDefsopt

StaticForeachStatement:
StaticForeach NoScopeNonEmptyStatement

StaticForeach:
static AggregateForeach
static RangeForeach

StaticAssert:
static assert ( ArgumentList ) ;

Traits

TraitsExpression:
__traits ( TraitsKeyword , TraitsArguments )

TraitsKeyword:
isAbstractClass
isArithmetic
isAssociativeArray
isFinalClass
isPOD
isNested
isFuture
isDeprecated
isFloating
isIntegral
isScalar
isStaticArray
isUnsigned
isDisabled
isVirtualFunction
isVirtualMethod
isAbstractFunction
isFinalFunction
isStaticFunction
isOverrideFunction
isTemplate
isRef
isOut
isLazy
isReturnOnStack
isCopyable
isZeroInit
isModule
isPackage
isCOMClass
hasMember
hasCopyConstructor
hasMoveConstructor
hasPostblit
identifier
fullyQualifiedName
getAliasThis
getAttributes
getBitfieldOffset
getBitfieldWidth
getFunctionAttributes
getFunctionVariadicStyle
getLinkage
getLocation
getMember
getOverloads
getParameterStorageClasses
getPointerBitmap
getCppNamespaces
getVisibility
getProtection
getTargetInfo
getVirtualFunctions
getVirtualMethods
getUnitTests
parent
child
classInstanceSize
classInstanceAlignment
getVirtualIndex
allMembers
derivedMembers
isSame
compiles
toType
initSymbol
parameters
fullyQualifiedName

TraitsArguments:
TraitsArgument
TraitsArgument , TraitsArguments

TraitsArgument:
AssignExpression
Type

Unit Tests

UnitTest:
unittest BlockStatement

D x86 Inline Assembler

AsmStatement:
asm FunctionAttributesopt { AsmInstructionListopt }

AsmInstructionList:
AsmInstruction ;
AsmInstruction ; AsmInstructionList

AsmInstruction:
Identifier : AsmInstruction
align IntegerExpression
even
naked
db Operands
ds Operands
di Operands
dl Operands
df Operands
dd Operands
de Operands
db StringLiteral
ds StringLiteral
di StringLiteral
dl StringLiteral
dw StringLiteral
dq StringLiteral
Opcode
Opcode Operands

Opcode:
Identifier
int
in
out

Operands:
Operand
Operand , Operands

IntegerExpression:
IntegerLiteral
Identifier

Register:
AL
AH
AX
EAX

BL
BH
BX
EBX

CL
CH
CX
ECX

DL
DH
DX
EDX

BP
EBP

SP
ESP

DI
EDI

SI
ESI

ES
CS
SS
DS
GS
FS

CR0
CR2
CR3
CR4

DR0
DR1
DR2
DR3
DR6
DR7

TR3
TR4
TR5
TR6
TR7

ST

ST(0)
ST(1)
ST(2)
ST(3)
ST(4)
ST(5)
ST(6)
ST(7)

MM0
MM1
MM2
MM3
MM4
MM5
MM6
MM7

XMM0
XMM1
XMM2
XMM3
XMM4
XMM5
XMM6
XMM7

Register64:
RAX
RBX
RCX
RDX

BPL
RBP

SPL
RSP

DIL
RDI

SIL
RSI

R8B
R8W
R8D
R8

R9B
R9W
R9D
R9

R10B
R10W
R10D
R10

R11B
R11W
R11D
R11

R12B
R12W
R12D
R12

R13B
R13W
R13D
R13

R14B
R14W
R14D
R14

R15B
R15W
R15D
R15

XMM8
XMM9
XMM10
XMM11
XMM12
XMM13
XMM14
XMM15

YMM0
YMM1
YMM2
YMM3
YMM4
YMM5
YMM6
YMM7

YMM8
YMM9
YMM10
YMM11
YMM12
YMM13
YMM14
YMM15

Operand:
AsmExp

AsmExp:
AsmLogOrExp
AsmLogOrExp ? AsmExp : AsmExp

AsmLogOrExp:
AsmLogAndExp
AsmLogOrExp || AsmLogAndExp

AsmLogAndExp:
AsmOrExp
AsmLogAndExp && AsmOrExp

AsmOrExp:
AsmXorExp
AsmOrExp | AsmXorExp

AsmXorExp:
AsmAndExp
AsmXorExp ^ AsmAndExp

AsmAndExp:
AsmEqualExp
AsmAndExp & AsmEqualExp

AsmEqualExp:
AsmRelExp
AsmEqualExp == AsmRelExp
AsmEqualExp != AsmRelExp

AsmRelExp:
AsmShiftExp
AsmRelExp < AsmShiftExp
AsmRelExp <= AsmShiftExp
AsmRelExp > AsmShiftExp
AsmRelExp >= AsmShiftExp

AsmShiftExp:
AsmAddExp
AsmShiftExp << AsmAddExp
AsmShiftExp >> AsmAddExp
AsmShiftExp >>> AsmAddExp

AsmAddExp:
AsmMulExp
AsmAddExp + AsmMulExp
AsmAddExp - AsmMulExp

AsmMulExp:
AsmBrExp
AsmMulExp * AsmBrExp
AsmMulExp / AsmBrExp
AsmMulExp % AsmBrExp

AsmBrExp:
AsmUnaExp
AsmBrExp [ AsmExp ]

AsmUnaExp:
AsmTypePrefix AsmExp
offsetof AsmExp
seg AsmExp
+ AsmUnaExp
- AsmUnaExp
! AsmUnaExp
~ AsmUnaExp
AsmPrimaryExp

AsmPrimaryExp:
IntegerLiteral
FloatLiteral
__LOCAL_SIZE
$
Register
Register : AsmExp
Register64
Register64 : AsmExp
DotIdentifier
this

DotIdentifier:
Identifier
Identifier . DotIdentifier
FundamentalType . Identifier

AsmTypePrefix:
near ptr
far ptr
word ptr
dword ptr
qword ptr
FundamentalType ptr

GccAsmStatement:
asm FunctionAttributesopt { GccAsmInstructionList }

GccAsmInstructionList:
GccAsmInstruction ;
GccAsmInstruction ; GccAsmInstructionList

GccAsmInstruction:
GccBasicAsmInstruction
GccExtAsmInstruction
GccGotoAsmInstruction

GccBasicAsmInstruction:
GccAsmStringExpression

GccExtAsmInstruction:
GccAsmStringExpression : GccAsmOperandsopt
GccAsmStringExpression : GccAsmOperandsopt : GccAsmOperandsopt
GccAsmStringExpression : GccAsmOperandsopt : GccAsmOperandsopt : GccAsmClobbersopt

GccGotoAsmInstruction:
GccAsmStringExpression : : GccAsmOperandsopt : GccAsmClobbersopt : GccAsmGotoLabelsopt

GccAsmStringExpression:
StringLiteral
( ConditionalExpression )

GccAsmOperands:
GccSymbolicNameopt GccAsmStringExpression ( AssignExpression )
GccSymbolicNameopt GccAsmStringExpression ( AssignExpression ) , GccAsmOperands

GccSymbolicName:
[ Identifier ]

GccAsmClobbers:
GccAsmStringExpression
GccAsmStringExpression , GccAsmClobbers

GccAsmGotoLabels:
Identifier
Identifier , GccAsmGotoLabels

Application Binary Interface

MangledName:
_D QualifiedName Type
_D QualifiedName Z        // Internal

QualifiedName:
SymbolFunctionName
SymbolFunctionName QualifiedName

SymbolFunctionName:
SymbolName
SymbolName TypeFunctionNoReturn
SymbolName M TypeModifiersopt TypeFunctionNoReturn

SymbolName:
LName
TemplateInstanceName
IdentifierBackRef
0                         // anonymous symbols

TemplateInstanceName:
TemplateID LName TemplateArgs Z

TemplateID:
__T
__U        // for symbols declared inside template constraint

TemplateArgs:
TemplateArg
TemplateArg TemplateArgs

TemplateArg:
TemplateArgX
H TemplateArgX

TemplateArgX:
T Type
V Type Value
S QualifiedName
X Number ExternallyMangledName

Values:
Value
Value Values

Value:
n
i Number
N Number
e HexFloat
c HexFloat c HexFloat
CharWidth Number _ HexDigits
A Number Values
S Number Values
f MangledName

HexFloat:
NAN
INF
NINF
N HexDigits P Exponent
HexDigits P Exponent

Exponent:
N Number
Number

HexDigits:
HexDigit
HexDigit HexDigits

HexDigit:
Digit
A
B
C
D
E
F

CharWidth:
a
w
d

Name:
Namestart
Namestart Namechars

Namestart:
_
Alpha

Namechar:
Namestart
Digit

Namechars:
Namechar
Namechar Namechars

LName:
Number Name
Number __S Number    // function-local parent symbols

Number:
Digit
Digit Number

Digit:
0
1
2
3
4
5
6
7
8
9

TypeBackRef:
Q NumberBackRef

IdentifierBackRef:
Q NumberBackRef

NumberBackRef:
lower-case-letter
upper-case-letter NumberBackRef

Type:
TypeModifiersopt TypeX
TypeBackRef

TypeX:
TypeArray
TypeStaticArray
TypeAssocArray
TypePointer
TypeFunction
TypeIdent
TypeClass
TypeStruct
TypeEnum
TypeTypedef
TypeDelegate
TypeVoid
TypeByte
TypeUbyte
TypeShort
TypeUshort
TypeInt
TypeUint
TypeLong
TypeUlong
TypeCent
TypeUcent
TypeFloat
TypeDouble
TypeReal
TypeIfloat
TypeIdouble
TypeIreal
TypeCfloat
TypeCdouble
TypeCreal
TypeBool
TypeChar
TypeWchar
TypeDchar
TypeNoreturn
TypeNull
TypeTuple
TypeVector

TypeModifiers:
Const
Wild
Wild Const
Shared
Shared Const
Shared Wild
Shared Wild Const
Immutable

Shared:
O

Const:
x

Immutable:
y

Wild:
Ng

TypeArray:
A Type

TypeStaticArray:
G Number Type

TypeAssocArray:
H Type Type

TypePointer:
P Type

TypeVector:
Nh Type

TypeFunction:
TypeFunctionNoReturn Type

TypeFunctionNoReturn:
CallConvention FuncAttrsopt Parametersopt ParamClose

CallConvention:
F       // D
U       // C
W       // Windows
R       // C++
Y       // Objective-C

FuncAttrs:
FuncAttr
FuncAttr FuncAttrs

FuncAttr:
FuncAttrPure
FuncAttrNothrow
FuncAttrRef
FuncAttrProperty
FuncAttrNogc
FuncAttrReturn
FuncAttrScope
FuncAttrTrusted
FuncAttrSafe
FuncAttrLive

FuncAttrPure:
Na

FuncAttrNogc:
Ni

FuncAttrNothrow:
Nb

FuncAttrProperty:
Nd

FuncAttrRef:
Nc

FuncAttrReturn:
Nj

FuncAttrScope:
Nl

FuncAttrTrusted:
Ne

FuncAttrSafe:
Nf

FuncAttrLive:
Nm

Parameters:
Parameter
Parameter Parameters

Parameter:
Parameter2
M Parameter2     // scope
Nk Parameter2    // return

Parameter2:
Type
I Type     // in
J Type     // out
K Type     // ref
L Type     // lazy

ParamClose:
X     // variadic T t...) style
Y     // variadic T t,...) style
Z     // not variadic

TypeIdent:
I QualifiedName

TypeClass:
C QualifiedName

TypeStruct:
S QualifiedName

TypeEnum:
E QualifiedName

TypeTypedef:
T QualifiedName

TypeDelegate:
D TypeModifiersopt TypeFunction

TypeVoid:
v

TypeByte:
g

TypeUbyte:
h

TypeShort:
s

TypeUshort:
t

TypeInt:
i

TypeUint:
k

TypeLong:
l

TypeUlong:
m

TypeCent:
zi

TypeUcent:
zk

TypeFloat:
f

TypeDouble:
d

TypeReal:
e

TypeIfloat:
o

TypeIdouble:
p

TypeIreal:
j

TypeCfloat:
q

TypeCdouble:
r

TypeCreal:
c

TypeBool:
b

TypeChar:
a

TypeWchar:
u

TypeDchar:
w

TypeNoreturn:
Nn

TypeNull:
n

TypeTuple:
B Parameters Z

ImportC

EnumDeclaration:
enum Identifier : EnumBaseType EnumBody

EnumBaseType:
Type

Modules

Module:
ModuleDeclaration
ModuleDeclaration DeclDefs
DeclDefs

DeclDefs:
DeclDef
DeclDef DeclDefs

DeclDef:
AttributeSpecifier
Declaration
Constructor
Destructor
Postblit
Invariant
UnitTest
AliasThis
StaticConstructor
StaticDestructor
SharedStaticConstructor
SharedStaticDestructor
ConditionalDeclaration
DebugSpecification
VersionSpecification
MixinDeclaration
EmptyDeclaration

EmptyDeclaration:
;

Modules have a one-to-one correspondence with source files. When not explicitly set via a ModuleDeclaration, a module's name defaults to the name of the file stripped of its path and extension.

A module's name automatically acts as a namespace scope for its contents. Modules superficially resemble classes, but differ in that:

- Only one instance of a module exists, and it is statically allocated.

- Modules do not have virtual tables.

- Modules do not inherit, do not have super modules, etc.

- A source file may contain only one module.

- Symbols in a module can be imported.

- Modules are always compiled at global scope and are unaffected by surrounding attributes or other modifiers.

Modules can be grouped into hierarchies called packages.

Modules offer several guarantees:

- The order in which modules are imported does not affect their semantics.

- The semantics of a module are not affected by the scope in which it is imported.

- If a module C imports modules A and B, any modifications to B will not silently change code in C that is dependent on A.

Module Declaration

The ModuleDeclaration sets the name of the module and what package it belongs to. If absent, the module name is taken to be the same name (stripped of path and extension) of the source file name.

ModuleDeclaration:
ModuleAttributesopt module ModuleFullyQualifiedName ;

ModuleAttributes:
ModuleAttribute
ModuleAttribute ModuleAttributes

ModuleAttribute:
DeprecatedAttribute
UserDefinedAttribute

ModuleFullyQualifiedName:
ModuleName
Packages . ModuleName

ModuleName:
Identifier

Packages:
PackageName
Packages . PackageName

PackageName:
Identifier

The Identifiers preceding the rightmost Identifier are the Packages that the module is in. The packages correspond to directory names in the source file path. Package and module names cannot be Keywords.

If present, the ModuleDeclaration must be the first and only such declaration in the source file, and may be preceded only by comments and #line directives.

Example:

module c.stdio; // module stdio in the c package

By convention, package and module names are all lower case. This is because these names have a one-to-one correspondence with the operating system's directory and file names, and many file systems are not case sensitive. Using all lower case package and module names will avoid or minimize problems when moving projects between dissimilar file systems.

If the file name of a module is an invalid module name (e.g. foo-bar.d), use a module declaration to set a valid module name:

module foo_bar;

Implementation Defined:

1. The mapping of package and module identifiers to directory and file names.

Best Practices:

1. PackageNames and ModuleNames should be composed of the ASCII characters lower case letters, digits or _ to ensure maximum portability and compatibility with various file systems.

2. The file names for packages and modules should be composed only of the ASCII lower case letters, digits, and _s, and should not be a Keyword.

Deprecated modules

A ModuleDeclaration can have an optional DeprecatedAttribute. The compiler will produce a message when the deprecated module is imported.

deprecated module foo;

module bar;
import foo;  // Deprecated: module foo is deprecated

A DeprecatedAttribute can have an optional AssignExpression argument to provide a more informative message. The AssignExpression must evaluate to a string at compile time.

deprecated("Please use foo2 instead.")
module foo;

module bar;
import foo;  // Deprecated: module foo is deprecated - Please use foo2 instead.

Implementation Defined:

1. How the deprecation messages are presented to the user.

Import Declaration

Symbols from one module are made available in another module by using the ImportDeclaration:

ImportDeclaration:
import ImportList ;
static import ImportList ;

ImportList:
Import
ImportBindings
Import , ImportList

Import:
ModuleFullyQualifiedName
ModuleAliasIdentifier = ModuleFullyQualifiedName

ImportBindings:
Import : ImportBindList

ImportBindList:
ImportBind
ImportBind , ImportBindList

ImportBind:
Identifier
Identifier = Identifier

ModuleAliasIdentifier:
Identifier

There are several forms of the ImportDeclaration, from generalized to fine-grained importing.

The order in which ImportDeclarations occur has no significance.

ModuleFullyQualifiedNames in the ImportDeclaration must be fully qualified with whatever packages they are in. They are not considered to be relative to the module that imports them.

Implementation Defined:

1. How the compiler resolves the package and module identifiers in an import declaration to its corresponding source files.

Symbol Name Lookup

The simplest form of importing is to just list the modules being imported:

module myapp.main;

import std.stdio; // import module stdio from package std

class Foo : BaseClass
{
import myapp.foo;  // import module myapp.foo in this class' scope
void bar ()
{
import myapp.bar;  // import module myapp.bar in this function' scope
writeln("hello!");  // calls std.stdio.writeln
}
}

When a symbol name is used unqualified, a two-phase lookup is used. First, the module scope is searched, starting from the innermost scope. For example, in the previous example, while looking for writeln, the order will be:

- Declarations inside bar.

- Declarations inside Foo.

- Declarations inside BaseClass.

- Declarations at module scope.

If the first lookup isn't successful, a second one is performed on imports. In the second lookup phase inherited scopes are ignored. This includes the scope of base classes and interfaces (in this example, BaseClass's imports would be ignored), as well as imports in mixed-in template.

Symbol lookup stops as soon as a matching symbol is found. If two symbols with the same name are found at the same lookup phase, this ambiguity will result in a compilation error.

module A;
void foo();
void bar();

module B;
void foo();
void bar();

module C;
import A;
void foo();
void test()
{
foo(); // C.foo() is called, it is found before imports are searched
bar(); // A.bar() is called, since imports are searched
}

module D;
import A;
import B;
void test()
{
foo();   // error, A.foo() or B.foo() ?
A.foo(); // ok, call A.foo()
B.foo(); // ok, call B.foo()
}

module E;
import A;
import B;
alias foo = B.foo;
void test()
{
foo();   // call B.foo()
A.foo(); // call A.foo()
B.foo(); // call B.foo()
}

Public Imports

By default, imports are private. This means that if module A imports module B, and module B imports module C, then names inside C are visible only inside B and not inside A.

An import can be explicitly declared public, which will cause names from the imported module to be visible to further imports. So in the above example where module A imports module B, if module B publicly imports module C, names from C will be visible in A as well.

All symbols from a publicly imported module are also aliased in the importing module. Thus in the above example if C contains the name foo, it will be accessible in A as foo, B.foo and C.foo.

For another example:

module W;
void foo() { }

module X;
void bar() { }

module Y;
import W;
public import X;
...
foo();  // calls W.foo()
bar();  // calls X.bar()

module Z;
import Y;
...
foo();   // error, foo() is undefined
bar();   // ok, calls X.bar()
X.bar(); // ditto
Y.bar(); // ok, Y.bar() is an alias to X.bar()

Static Imports

A static import requires the use of a fully qualified name to reference the module's names:

static import std.stdio;

void main()
{
writeln("hello!");           // error, writeln is undefined
std.stdio.writeln("hello!"); // ok, writeln is fully qualified
}

Renamed Imports

A local name for an import can be given, through which all references to the module's symbols must be qualified with:

import io = std.stdio;

void main()
{
io.writeln("hello!");        // ok, calls std.stdio.writeln
std.stdio.writeln("hello!"); // error, std is undefined
writeln("hello!");           // error, writeln is undefined
}

Best Practices: Renamed imports are handy when dealing with very long import names.

Selective Imports

Specific symbols can be exclusively imported from a module and bound into the current namespace:

import std.stdio : writeln, foo = write;

void main()
{
std.stdio.writeln("hello!"); // error, std is undefined
writeln("hello!");           // ok, writeln bound into current namespace
write("world");              // error, write is undefined
foo("world");                // ok, calls std.stdio.write()
fwritefln(stdout, "abc");    // error, fwritefln undefined
}

static cannot be used with selective imports.

Renamed and Selective Imports

When renaming and selective importing are combined:

import io = std.stdio : foo = writeln;

void main()
{
writeln("bar");           // error, writeln is undefined
std.stdio.foo("bar");     // error, foo is bound into current namespace
std.stdio.writeln("bar"); // error, std is undefined
foo("bar");               // ok, foo is bound into current namespace,
// FQN not required
io.writeln("bar");        // ok, io=std.stdio bound the name io in
// the current namespace to refer to the entire
//   module
io.foo("bar");            // error, foo is bound into current namespace,
// foo is not a member of io
}

Scoped Imports

Import declarations may be used at any scope. For example:

void main()
{
import std.stdio;
writeln("bar");
}

The imports are looked up to satisfy any unresolved symbols at that scope. Imported symbols may hide symbols from outer scopes.

In function scopes, imported symbols only become visible after the import declaration lexically appears in the function body. In other words, imported symbols at function scope cannot be forward referenced.

void main()
{
void writeln(string) {}
void foo()
{
writeln("bar"); // calls main.writeln
import std.stdio;
writeln("bar"); // calls std.stdio.writeln
void writeln(string) {}
writeln("bar"); // calls main.foo.writeln
}
writeln("bar"); // calls main.writeln
std.stdio.writeln("bar");  // error, std is undefined
}

Module Scope Operator

A leading dot (.) causes the identifier to be looked up in the module scope.

const int x = 1;

void main()
{
int x = 5;
assert(x == 5); // main.x, not global x
assert(.x == 1); // global x
}

Static Construction and Destruction

Static constructors are executed to initialize a module's state. Static destructors terminate a module's state.

A module may have multiple static constructors and static destructors. The static constructors are run in lexical order, the static destructors are run in reverse lexical order.

Non-shared static constructors and destructors are run whenever threads are created or destroyed, including the main thread.

Shared static constructors are run once before main() is called. Shared static destructors are run after the main() function returns.

import resource;

Resource x;
shared Resource y;
__gshared Resource z;

static this()  // non-shared static constructor
{
x = acquireResource();
}

shared static this()  // shared static constructor
{
y = acquireSharedResource();
z = acquireSharedResource();
}

static ~this()  // non-shared static destructor
{
releaseResource(x);
}

shared static ~this()   // shared static destructor
{
releaseSharedResource(y);
releaseSharedResource(z);
}

Best Practices:

1. Shared static constructors and destructors are used to initialize and terminate shared global data.

2. Non-shared static constructors and destructors are used to initialize and terminate thread local data.

Order of Static Construction

Shared static constructors on all modules are run before any non-shared static constructors.

The order of static initialization is implicitly determined by the import declarations in each module. Each module is assumed to depend on any imported modules being statically constructed first. There is no other order imposed on the execution of module static constructors.

Cycles (circular dependencies) in the import declarations are allowed so long as neither, or one, but not both, of the modules, contains static constructors or static destructors. Violation of this rule will result in a runtime exception.

Implementation Defined:

1. An implementation may provide a means of overriding the cycle detection abort. A typical method uses the D Runtime switch --DRT-oncycle=... where the following behaviors are supported:

1. abort The default behavior. The normal behavior as described in the previous section.

2. print Print all cycles detected, but do not abort execution. When cycles are present, the order of static construction is implementation defined, and not guaranteed to be valid.

3. ignore Do not abort execution or print any cycles. When cycles are present, the order of static construction is implementation defined, and not guaranteed to be valid.

Best Practices:

1. Avoid cyclical imports where practical. They can be an indication of poor decomposition of a program's structure into independent modules. Two modules that import each other can often be reorganized into three modules without cycles, where the third contains the declarations needed by the other two.

Order of Static Construction within a Module

Within a module, static construction occurs in the lexical order in which they appear.

Order of Static Destruction

This is defined to be in exactly the reverse order of static construction. Static destructors for individual modules will only be run if the corresponding static constructor successfully completed.

Shared static destructors are executed after static destructors.

Order of Unit tests

Unit tests are run in the lexical order in which they appear within a module.

Mixin Declaration

MixinDeclaration:
mixin ( ArgumentList ) ;

Each AssignExpression in the ArgumentList is evaluated at compile time, and the result must be representable as a string. The resulting strings are concatenated to form a string. The text contents of the string must be compilable as valid DeclDefs, and is compiled as such.

The content of a mixin cannot be forward referenced by other DeclDefs of the same scope because it is not yet pulled into the AST.

class B : A {}      // Error: undefined identifier `A`
mixin ("class A {}");

Forward references may only work in function bodies because they are processed after the declarations:

void v()
{
class B : A {}
}
mixin ("class A {}");

Package Module

A package module can be used to publicly import other modules, while providing a simpler import syntax. This enables the conversion of a module into a package of modules, without breaking existing code which uses that module. Example of a set of library modules:
libweb/client.d:

module libweb.client;

void runClient() { }

libweb/server.d:

module libweb.server;

void runServer() { }

libweb/package.d:

module libweb;

public import libweb.client;
public import libweb.server;

The package module's file name must be package.d. The module name is declared to be the fully qualified name of the package. Package modules can be imported just like any other modules:
test.d:

module test;

// import the package module
import libweb;

void main()
{
runClient();
runServer();
}

A package module can be nested inside of a sub-package:
libweb/utils/package.d:

// must be declared as the fully qualified name of the package, not just 'utils'
module libweb.utils;

// publicly import modules from within the 'libweb.utils' package.
public import libweb.utils.conv;
public import libweb.utils.text;

The package module can then be imported with the standard module import declaration:
test.d:

module test;

// import the package module
import libweb.utils;

void main() { }

Declarations

Grammar

Declaration can be used inside a function body for a DeclarationStatement, as well as outside a function as it is included in DeclDef.

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

Aggregates

AggregateDeclaration:
ClassDeclaration
InterfaceDeclaration
StructDeclaration
UnionDeclaration

Variable Declarations

VarDeclarations:
StorageClassesopt BasicType TypeSuffixesopt IdentifierInitializers ;
AutoDeclaration

IdentifierInitializers:
IdentifierInitializer
IdentifierInitializer , IdentifierInitializers

IdentifierInitializer:
Identifier
Identifier TemplateParametersopt = Initializer

Declarator:
TypeSuffixesopt Identifier

See also:

- Declaration Syntax

- AutoDeclaration

- Variable Templates

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

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

See Pointers, Arrays and TypeSuffix.

Function Pointers

Function Pointers 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 not supported. The following C declarations are for comparison only:

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

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

Rationale:

- In D types are straightforward to read from right to left, unlike in C where parentheses are sometimes required and the type is read iteratively using the clockwise/spiral rule.

- For a C function pointer declaration a (*b)(c); a C parser needs to attempt a type lookup in order to parse it unambiguously - it could be a call to a function called a which returns a function pointer, which is immediately called. D function pointer syntax is unambiguous, avoiding the need for types to be forward declared.

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

This is in contrast to C:

int x, *y;  // x is an int, y is a pointer to int
int x[], y; // x is an array/pointer, y is an int

Initialization

When no Initializer is given, a variable is set to the default .init value for its type.

Initializers

Initializer:
VoidInitializer
NonVoidInitializer

NonVoidInitializer:
ArrayInitializer
StructInitializer
AssignExpression

A variable can be initialized with a NonVoidInitializer. An ArrayInitializer or StructInitializer takes precedence over an expression initializer.

struct S { int i; }

S s = {}; // struct initializer, not a function literal expression
S[] a = [{2}]; // array initializer holding a struct initializer
//a = [{2}]; // invalid array literal expression

Void Initialization

VoidInitializer:
void

Normally a variable will be initialized. However, if a variable initializer is void, the variable is not initialized. Void initializers for variables with a type that may contain unsafe values (such as types with pointers) are 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.

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]

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.

Alias Declarations

Note: New code should use the AliasAssignments form only.

AliasDeclaration:
alias StorageClassesopt BasicType TypeSuffixesopt Identifiers ;
alias StorageClassesopt BasicType FuncDeclarator ;
alias AliasAssignments ;

Identifiers:
Identifier
Identifier , Identifiers

AliasAssignments:
AliasAssignment
AliasAssignments , AliasAssignment

AliasAssignment:
Identifier TemplateParametersopt = StorageClassesopt Type
Identifier TemplateParametersopt = FunctionLiteral
Identifier TemplateParametersopt = StorageClassesopt Type 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

Variables can be aliased, expressions cannot:

int i = 0;
alias a = i; // OK
alias b = a; // alias a variable alias
a++;
b++;
assert(i == 2);

//alias c = i * 2; // error
//alias d = i + i; // error

Members of an aggregate can be aliased, however non-static field aliases cannot be accessed outside their parent type.

struct S
{
static int i = 0;
int j;
alias a = j; // OK

void inc() { a++; }
}

alias a = S.i; // OK
a++;
assert(S.i == 1);

alias b = S.j; // allowed
static assert(b.offsetof == 0);
//b++;   // error, no instance of S
//S.a++; // error, no instance of S

S s = S(5);
s.inc();
assert(s.j == 6);
//alias c = s.j; // scheduled for deprecation

Aliasing a Function Type

Function types can be aliased:

alias Fun = int(string);
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));

Type 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 : writeln;

void fun(int v = 6)
{
writeln("v: ", v);
}

void main()
{
fun();  // prints v: 6

alias Foo = void function(int=7);
Foo foo = &fun;
foo();  // prints v: 7
foo(8); // prints v: 8
}

import std.stdio : writefln;

void main()
{
fun(4);          // prints a: 4, b: 6, c: 7

Bar bar = &fun;
//bar(4);           // compilation error, because the `Bar` alias
// requires an explicit 2nd argument
bar(4, 5);          // prints a: 4, b: 5, c: 9
bar(4, 5, 6);       // prints a: 4, b: 5, c: 6

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

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

void fun(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.

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().
}

ref Variables

From version 2.111, ref can be used to declare local, static, extern, and global variables.

struct S { int a; }

void main()
{
S s;
ref int r = s.a;
r = 3;
assert(s.a == 3);
}

Best Practices: Use a ref variable instead of a pointer when pointer arithmetic is not needed.

auto ref can also be used to declare local, static, extern, and global variables.

void f()
{
auto ref x = 0;
auto ref y = x;
static assert(!__traits(isRef, x));
static assert( __traits(isRef, y));
}

See also: auto ref template function parameters.

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

Types

Grammar

D is statically typed. Every expression has a type. Types constrain the values an expression can hold, and determine the semantics of operations on those values.

Type:
TypeCtorsopt BasicType TypeSuffixesopt

TypeCtors:
TypeCtor
TypeCtor TypeCtors

TypeCtor:
const
immutable
inout
shared

BasicType:
FundamentalType
. QualifiedIdentifier
QualifiedIdentifier
Typeof
Typeof . QualifiedIdentifier
TypeCtor ( Type )
Vector
TraitsExpression
MixinType

Vector:
__vector ( VectorBaseType )

VectorBaseType:
Type

FundamentalType:
bool
byte
ubyte
short
ushort
int
uint
long
ulong
cent
ucent
char
wchar
dchar
float
double
real
ifloat
idouble
ireal
cfloat
cdouble
creal
void

TypeSuffixes:
TypeSuffix TypeSuffixesopt

TypeSuffix:
*
[ ]
[ AssignExpression ]
[ AssignExpression .. AssignExpression ]
[ Type ]
delegate Parameters MemberFunctionAttributesopt
function Parameters FunctionAttributesopt

QualifiedIdentifier:
Identifier
Identifier . QualifiedIdentifier
TemplateInstance
TemplateInstance . QualifiedIdentifier
Identifier [ AssignExpression ]
Identifier [ AssignExpression ] . QualifiedIdentifier

- Basic Data Types are leaf types.

- Derived Data Types build on leaf types.

- User-Defined Types are aggregates of basic and derived types.

Basic Data Types

Basic Data Types

Keyword

Default Initializer (.init)

Description

void

no default initializer

void has no value

bool

false

boolean value

byte

0

signed 8 bits

ubyte

0u

unsigned 8 bits

short

0

signed 16 bits

ushort

0u

unsigned 16 bits

int

0

signed 32 bits

uint

0u

unsigned 32 bits

long

0L

signed 64 bits

ulong

0uL

unsigned 64 bits

cent

0

signed 128 bits

ucent

0u

unsigned 128 bits

float

float.nan

32 bit floating point

double

double.nan

64 bit floating point

real

real.nan

largest floating point size available

ifloat

float.nan*1.0i

imaginary float

idouble

double.nan*1.0i

imaginary double

ireal

real.nan*1.0i

imaginary real

cfloat

float.nan+float.nan*1.0i

a complex number of two float values

cdouble

double.nan+double.nan*1.0i

complex double

creal

real.nan+real.nan*1.0i

complex real

char

'\xFF'

unsigned 8 bit (UTF-8 code unit)

wchar

'\uFFFF'

unsigned 16 bit (UTF-16 code unit)

dchar

'\U0000FFFF'

unsigned 32 bit (UTF-32 code unit)

Endianness of basic types is part of the ABI

Implementation Defined: The real floating point type has at least the range and precision of the double type. On x86 CPUs it is often implemented as the 80 bit Extended Real type supported by the x86 FPU.

Note: 128-bit integer types cent and ucent have been deprecated.

Note: Complex and imaginary types ifloat, idouble, ireal, cfloat, cdouble, and creal have been deprecated in favor of std.complex.Complex.

Derived Data Types

- Pointers

- Static Arrays

- Dynamic Arrays

- Associative Arrays

- Function Types

- Delegate Types

- Type Sequences

int* p; // pointer
int[2] sa; // static array
int[] da; // dynamic array/slice

int[string] aa; // associative array
void function() fp; // function pointer

import std.meta : AliasSeq;
AliasSeq!(int, string) tsi; // type sequence instance

Pointers

A pointer value is a memory address. A pointer to type T has a value which is a reference to an instance of type T. It is commonly called a pointer to T and its type is T*. To access the pointed-to value, use the * dereference operator:

int* p;

assert(p == null);
p = new int(5);
assert(p != null);

assert(*p == 5);
(*p)++;
assert(*p == 6);

If a pointer has a null value, it is not pointing to valid data.

When a pointer to T is dereferenced, it must either have a null value, or point to a valid instance of type T.

Implementation Defined:

1. The behavior when a null pointer is dereferenced. Typically the program will be aborted - that is required in @safe code.

Undefined Behavior: dereferencing a pointer that is not null and does not point to a valid instance of type T.

To set a pointer to point at an existing lvalue, use the & address of operator:

int i = 2;
int* p = &i;

assert(p == &i);
assert(*p == 2);
*p = 4;
assert(i == 4);

See also:

- Pointer Arithmetic

- Pointer Slicing

- Pointer Conversions

Best Practices: Use a ref function parameter or a ref local variable when the address doesn't escape.

User-Defined Types

- Enums

- Structs and Unions

- Classes

- Interfaces

Type Conversions
See also: CastExpression.
Pointer Conversions

Any pointer implicitly converts to a void pointer - see below.

Casting between pointers and non-pointers is allowed. Some pointer casts are disallowed in @safe code.

Best Practices: do not cast any pointer to a non-pointer type that points to data allocated by the garbage collector.

Implicit Conversions

Implicit conversions are used to automatically convert types as required. The rules for integers are detailed in the next sections.

- An enum can be implicitly converted to its base type (but going the other way requires an explicit conversion).

- noreturn implicitly converts to any type.

- Static and dynamic arrays implicitly convert to void arrays. The void element type for the array may need a type qualifier, depending on the source element type:

- An array with non-mutable elements will implicitly convert to const(void)[], but not void[].

- An array with shared elements will implicitly convert to shared(void)[], but not void[].

- Any pointer implicitly converts to a void pointer. As for void arrays, the void element type may need a type qualifier.

- Function pointers and delegates can convert to covariant types.

void main()
{
noreturn n;
int i = n;
void* p = &i;

const int[] a;
const(void)[] cv = a;
//void[] va = a; // error
}

void f(int x) pure;
void function(int) fp = &f; // pure is covariant with non-pure

See also Implicit Qualifier Conversions.

Class Conversions

A derived class can be implicitly converted to its base class, but going the other way requires an explicit cast. For example:

class Base {}
class Derived : Base {}
Base bd = new Derived();              // implicit conversion
Derived db = cast(Derived)new Base(); // explicit conversion

A dynamic array, say x, of a derived class can be implicitly converted to a dynamic array, say y, of a base class iff elements of x and y are qualified as being either both const or both immutable.

class Base {}
class Derived : Base {}
const(Base)[] ca = (const(Derived)[]).init; // `const` elements
immutable(Base)[] ia = (immutable(Derived)[]).init; // `immutable` elements

A static array, say x, of a derived class can be implicitly converted to a static array, say y, of a base class iff elements of x and y are qualified as being either both const or both immutable or both mutable (neither const nor immutable).

class Base {}
class Derived : Base {}
Base[3] ma = (Derived[3]).init; // mutable elements
const(Base)[3] ca = (const(Derived)[3]).init; // `const` elements
immutable(Base)[3] ia = (immutable(Derived)[3]).init; // `immutable` elements

Integer Promotions

Integer Promotions are conversions of the following types:

Integer Promotions

from

to

bool

int

byte

int

ubyte

int

short

int

ushort

int

char

int

wchar

int

dchar

uint

If an enum has as a base type one of the types in the left column, it is converted to the type in the right column.

Integer promotion applies to each operand of a binary expression:

void fun()
{
byte a;
auto b = a + a;
static assert(is(typeof(b) == int));
// error: can't implicitly convert expression of type int to byte:
//byte c = a + a;

ushort d;
// error: can't implicitly convert expression of type int to ushort:
//d = d * d;
int e = d * d; // OK
static assert(is(typeof(int() * d) == int));

dchar f;
static assert(is(typeof(f - f) == uint));
}

Rationale:

- 32-bit integer operations are often faster than smaller integer types for single variables on modern architectures.

- Promotion helps avoid accidental overflow which is more common with small integer types.

Usual Arithmetic Conversions

The usual arithmetic conversions convert operands of binary operators to a common type. The operands must already be of arithmetic types. The following rules are applied in order, looking at the base type:

1. If either operand is real, the other operand is converted to real.

2. Else if either operand is double, the other operand is converted to double.

3. Else if either operand is float, the other operand is converted to float.

4. Else the integer promotions above are done on each operand, followed by:

1. If both are the same type, no more conversions are done.

2. If both are signed or both are unsigned, the smaller type is converted to the larger.

3. If the signed type is larger than the unsigned type, the unsigned type is converted to the signed type.

4. The signed type is converted to the unsigned type.

Rationale: The above rules follow C99, which makes porting code from C easier.

Example: Signed and unsigned conversions:

int i;
uint u;
static assert(is(typeof(i + u) == uint));
static assert(is(typeof(short() + u) == uint));
static assert(is(typeof(ulong() + i) == ulong));
static assert(is(typeof(long() - u) == long));
static assert(is(typeof(long() * ulong()) == ulong));

Example: Floating point:

float f;
static assert(is(typeof(f + ulong()) == float));

double d;
static assert(is(typeof(f * d) == double));
static assert(is(typeof(real() / d) == real));

Enum Operations

If one or both of the operand types is an enum after undergoing the above conversions, the result type is determined as follows:

1. If the operands are the same type, the result will be of that type.

2. If one operand is an enum and the other is the base type of that enum, the result is the base type.

3. If the two operands are different enums, the result is the closest base type common to both. A base type being closer means there is a shorter sequence of conversions to base type to get there from the original type.

enum E { a, b, c }
enum F { x, y }

void test()
{
E e = E.a;
e = e | E.c;
//e = e + 4; // error, can't assign int to E
int i = e + 4;
e += 4; // OK, see below

F f;
//f = e | f; // error, can't assign int to F
i = e | f;
}

Note: Above, e += 4 compiles because the operator assignment is equivalent to e = cast(E)(e + 4).

Integer Type Conversions

An integer of type I implicitly converts to another integer type J when J.sizeof >= I.sizeof.

void f(byte b, ubyte ub, short s)
{
b = ub; // OK, bit pattern same
ub = b; // OK, bit pattern same
s = b; // OK, widening conversion
b = s; // error, implicit narrowing
}

Integer values cannot be implicitly converted to another type that cannot represent the integer bit pattern after integral promotion. For example:

ubyte  u1 = -1; // error, -1 cannot be represented in a ubyte
ushort u2 = -1; // error, -1 cannot be represented in a ushort
uint   u3 = -1; // ok, -1 can be represented in an int, which can be converted to a uint
ulong  u4 = -1; // ok, -1 can be represented in a long, which can be converted to a ulong

Floating Point Type Conversions

- Integral types implicitly convert to floating point types.

- Floating point types cannot be implicitly converted to integral types.

void f(int i, float f)
{
f = i; // OK
i = f; // error
}

- Complex or imaginary floating point types cannot be implicitly converted to non-complex floating point types.

- Non-complex floating point types cannot be implicitly converted to imaginary floating point types.

Value Range Propagation

Besides type-based implicit conversions, D allows certain integer expressions to implicitly convert to a narrower type after integer promotion. This works by analysing the minimum and maximum possible range of values for each expression. If that range of values matches or is a subset of a narrower target type's value range, implicit conversion is allowed. If a subexpression is known at compile-time, that can further narrow the range of values.

void fun(char c, int i, ubyte b)
{
// min is c.min + 100 > short.min
// max is c.max + 100 < short.max
short s = c + 100; // OK

ubyte j = i & 0x3F; // OK, 0 ... 0x3F
//ubyte k = i & 0x14A; // error, 0x14A > ubyte.max
ushort k = i & 0x14A; // OK

k = i & b; // OK, 0 ... b.max
//b = b + b; // error, b.max + b.max > b.max
s = b + b; // OK, 0 ... b.max + b.max
}

Note the implementation does not track the range of possible values for mutable variables:

void fun(int i)
{
ushort s = i & 0xff; // OK
// s is now assumed to be s.min ... s.max, not 0 ... 0xff
//ubyte b = s; // error
ubyte b = s & 0xff; // OK

const int c = i & 0xff;
// c's range is fixed and known
b = c; // OK
}

- For more information, see the dmc article.

- See also: https://en.wikipedia.org/wiki/Value_range_analysis.

void

A void value cannot be accessed directly. The void type is notably used for:

- The return type of a function that doesn't have a result.

- The base type for an untyped pointer - see Pointer Conversions.

- Void arrays.

- Ignoring a value by casting to void.

void.sizeof is 1 (not 0).

Rationale: The size must be 1 to make void* arithmetic work like it does in C. It also makes the length of a void array equivalent to the number of bytes in the array.

bool

The bool type is a byte-size type that can only hold the value true or false.

The only operators that can accept operands of type bool are: & |, ^, &=, |=, ^=, !, &&, ||, and ?:.

A bool value can be implicitly converted to any integral type, with false becoming 0 and true becoming 1.

The numeric literals 0 and 1 can be implicitly converted to the bool values false and true, respectively. Casting an expression to bool means testing !=0 for arithmetic types, and !=null for pointers or references.

Undefined Behavior:

- Interpreting a value with a byte representation other than 0 or 1 as bool (e.g. an overlapped union field).

- Reading a void-initialized bool.

byte i = 2;
bool b = cast(bool) i; // OK, same as `i != 0`
assert(b);

bool* p = cast(bool*) &i; // unsafe cast
// `*p` holds 0x2, an invalid bool value
// reading `*p` is undefined behavior

Function Types

A function type has the form:

StorageClassesopt Type Parameters FunctionAttributesopt

Function types are not included in the Type grammar. A function type e.g. int(int) can be aliased. A function type is only used for type tests or as the target type of a pointer.

Instantiating a function type is illegal. Instead, a pointer to function or delegate can be used. Those have these type forms respectively:

Type function Parameters FunctionAttributesopt
Type delegate Parameters MemberFunctionAttributesopt

void f(int);
alias Fun = void(int);
static assert(is(typeof(f) == Fun));
static assert(is(Fun* == void function(int)));

See Function Pointers.

Delegates

Delegates are an aggregate of two pieces of data, a context pointer and a function pointer. A valid delegate holds either:

- An object reference and a pointer to a non-static member function. The object reference forms the this pointer when the function is called.

- A pointer to a closure and a pointer to a nested function.

The .ptr property of a delegate will return the context pointer value as a void*.

The .funcptr property of a delegate will return the function pointer value as a function type.

Delegates are declared and initialized similarly to function pointers:

void func(int) {}
void function(int) fp; // fp is a function pointer
void delegate(int) dg; // dg is a delegate to a function

class OB
{
void member(int) {}
}

void main()
{
OB o = new OB;

fp = &func; // fp points to function `func`

dg = &o.member; // dg is a delegate to object `o` and member function `member`
assert(dg.ptr == cast(void*) o);
assert(dg.funcptr == &OB.member);

dg = (int i) { o.member(i); }; // dg holds a delegate literal with main's execution context
}

Delegates cannot be initialized with static member functions or non-member functions.

Delegates are called analogously to function pointers:

fp(3);   // call func(3)
dg(3);   // call o.member(3)

See:

- Delegates and Closures

- Method Delegates

The equivalent of member function pointers can be constructed using anonymous lambda functions:

class C
{
int a;
int foo(int i) { return i + a; }
}

// mfp is the member function pointer
auto mfp = function(C self, int i) { return self.foo(i); };
auto c = new C();  // create an instance of C
mfp(c, 1);  // and call c.foo(1)

typeof

Typeof:
typeof ( Expression )
typeof ( return )

The first form gives the type of an expression. It can be used anywhere a BasicType is expected, such as in a declaration. It is also useful as a PrimaryExpression in a sub-expression. For example:

void func(int i)
{
typeof(i) j;       // j is of type int
typeof(3 + 6.0) x; // x is of type double

// as part of a derived type:
typeof(1)* p;
static assert(is(typeof(p) == int*));
typeof(p)[int] aa;
static assert(is(typeof(aa) == int*[int]));

auto d = cast(typeof(1.0)) i; // cast i to double
static assert(is(typeof(d) == double));

// as a sub-expression:
static assert(typeof('c').sizeof == 1); // char.sizeof
Exception[2] sa;
Exception ex = new typeof(sa[0])("message"); // new Exception("message")
}

Expression is not evaluated, it is used purely to generate the type:

void main()
{
int i = 1;
typeof(++i) j; // j is declared to be an int, i is not incremented
assert(i == 1);
}

If Expression is a ValueSeq, typeof will produce a TypeSeq containing the types of each element.

typeof(null) is useful to get the type of the null literal.

Best Practices: Typeof is most useful in writing generic template code.

Special Cases

1. typeof(return) will, when inside a function scope, give the return type of that function.

2. typeof(this) will generate the type of what this would be in a non-static member function, even if not in a member function.

3. Analogously, typeof(super) will generate the type of what super would be in a non-static member function.

class A { }

class B : A
{
typeof(this) x;  // x is declared to be a B
typeof(super) y; // y is declared to be an A
}

struct C
{
static typeof(this) z;  // z is declared to be a C

typeof(super) q; // error, no super struct for C
}

typeof(this) r;   // error, no enclosing struct or class

If the expression is a Property Function, typeof gives its return type.

struct S
{
@property int foo() { return 1; }
}
typeof(S.foo) n;  // n is declared to be an int

If the expression is a Template, typeof gives the type void.

template t {}
static assert(is(typeof(t) == void));

Mixin Types

MixinType:
mixin ( ArgumentList )

Each AssignExpression in the ArgumentList is evaluated at compile time, and the result must be representable as a string. The resulting strings are concatenated to form a string. The text contents of the string must be compilable as a valid Type, and is compiled as such.

void test(mixin("int")* p) // int* p
{
mixin("int")[] a;      // int[] a;
mixin("int[]") b;      // int[] b;
}

Aliased Types

size_t

size_t is an alias to one of the unsigned integral basic types, and represents a type that is large enough to represent an offset into all addressable memory.

ptrdiff_t

ptrdiff_t is an alias to the signed integral basic type the same size as size_t.

string

A string is a special case of an array.

noreturn

noreturn is the bottom type which can implicitly convert to any type, including void. A value of type noreturn will never be produced and the compiler can optimize such code accordingly.

noreturn.init lowers to assert(0).

A function that never returns has the return type noreturn. This can occur due to e.g. an infinite loop or always throwing an exception. A function returning type noreturn is covariant with a function returning any other type.

noreturn abort(string message);

int function(string) fp = &abort; // OK

int example(int i)
{
if (i < 0)
{
// abort does not return, so it doesn't need to produce an int
int val = abort("less than zero");
}
// ternary expression's common type is still int
return i != 0 ? 1024 / i : abort("calculation went awry.");
}

noreturn is defined as typeof(*null). This is because dereferencing a null literal (typically) halts execution.

See also: ThrowExpression

Properties

Common Properties

Every symbol, type, and expression has properties that can be queried:

Property

Description

.mangleof

string representing the ‘mangled’ representation of the type

.stringof

string representing the source representation of the type

Examples

Expression

Value

int.mangleof

yields the string "i"

int.stringof

yields the string "int"

(1+2).stringof

yields the string "1 + 2"

Type Properties

Every type has properties that can be queried. Every expression also has these properties, which are equivalent to the properties of the expression's type. These properties are:

Property

Description

.init

initializer

.sizeof

size in bytes

.alignof

alignment size

Examples

Expression

Value

int.init

yields 0

int.sizeof

yields 4

(3).sizeof

yields 4 (because 3 is an int)

Numeric Properties

Properties for Integral Types

Property

Description

.max

maximum value

.min

minimum value

Properties for Floating Point Types

Property

Description

.infinity

infinity value

.nan

NaN - Not a Number value (other NaN values can be produced)

.dig

number of decimal digits of precision

.epsilon

smallest increment to the value 1

.mant_dig

number of bits in mantissa

.max_10_exp

maximum int value such that 10max_10_exp is representable

.max_exp

maximum int value such that 2max_exp-1 is representable

.min_10_exp

minimum int value such that 10min_10_exp is representable as a normalized value

.min_exp

minimum int value such that 2min_exp-1 is representable as a normalized value

.max

largest representable value that's not infinity

.min_normal

smallest representable normalized value that's not 0

.re

real part

.im

imaginary part

Floating Point Examples

Expression

Value

float.nan

yields the floating point NaN value

(2.5F).nan

yields the floating point NaN value

Derived Type Properties

See:

- Array Properties

- Associative Array Properties

- Vector Properties

User-Defined Type Properties

See:

- Class Properties

- Enum Properties

- Struct Properties

.init Property

.init produces a constant expression that is the default initializer. If applied to a type, it is the default initializer for that type. If applied to a variable or field, it is the default initializer for that variable or field's type. The default values for different kinds of types are described below:

Type

.init Value

char

'\xff'

dchar

'\xffff'

wchar

'\xffff'

Enum

first member value

Integers

0

Floating Point

NaN

Reference Types

null

Structs

each field's default value

Unions

first member value

Rationale: Where possible, the default value for a type is an invalid value.

int a;
int b = 1;

static assert(int.init == 0);
static assert(a.init == 0);
static assert(b.init == 0);

struct Foo
{
int a;
int b = 7;
}

static assert(Foo.init.a == 0);
static assert(Foo.init.b == 7);

.init vs Default Construction

Note that .init produces a default initialized object, not a default constructed one. If there is a default constructor for an object, it may produce a different value.

1. If T is a nested struct, the context pointer in T.init is null.

void main()
{
int x;
struct S
{
void foo() { x = 1; }  // access x in enclosing scope via context pointer
}
S s1;           // OK. S() correctly initialize its context pointer.
S s2 = S();     // OK. same as s1
s1.foo();       // OK

S s3 = S.init;  // Bad. the context pointer in s3 is null
s3.foo();       // Access violation
}

2. If T is a struct which has @disable this();, T.init might return a logically incorrect object.

struct S
{
int x;
@disable this();
this(int n) { x = n; }
invariant { assert(x > 0); }
void check() {}
}

void main()
{
//S s1;           // Error: variable s1 initializer required for type S
//S s2 = S();     // Error: constructor S.this is not callable
// because it is annotated with @disable
S s3 = S.init;  // Bad. s3.x == 0, and it violates the invariant of S
s3.check();     // Assertion failure
}

.stringof Property

.stringof produces a constant string that is the source representation of its prefix. If applied to a type, it is the string for that type. If applied to an expression, it is the source representation of that expression. The expression will not be evaluated.

module test;
import std.stdio;

struct Dog { }

enum Color { Red }

int i = 4;

void main()
{
writeln((1+2).stringof);       // "1 + 2"
writeln(test.stringof);        // "module test"
writeln(Dog.stringof);         // "Dog"
writeln(int.stringof);         // "int"
writeln((int*[5][]).stringof); // "int*[5][]"
writeln(Color.Red.stringof);   // "Red"
writeln((5).stringof);         // "5"

writeln((++i).stringof);       // "i += 1"
assert(i == 4);                // `++i` was not evaluated
}

Implementation Defined: The string representation for a type or expression can vary.

Best Practices: Do not use .stringof for code generation. Instead use the identifier trait, or one of the Phobos helper functions such as std.traits.fullyQualifiedName.

.sizeof Property

e.sizeof gives the size in bytes of the expression e.

When getting the size of a member, it is not necessary for there to be a this object:

struct S
{
int a;
}

static assert(S.a.sizeof == 4);
static assert(Object.sizeof == (void*).sizeof);

.sizeof applied to a class object returns the size of the class reference, not the class instantiation.

See also: __traits(classInstanceSize).

.alignof Property

.alignof gives the aligned size of an expression or type. For example, an aligned size of 1 means that it is aligned on a byte boundary, 4 means it is aligned on a 32 bit boundary.

See the align attribute for an example.

Implementation Defined: the actual aligned size.

Best Practices: Be particularly careful when laying out an object that must line up with an externally imposed layout. Data misalignment can result in particularly pernicious bugs. It's often worth putting in an assert to assure it is correct.

.mangleof Property

Mangling refers to how a symbol is represented in text form in the generated object file. .mangleof returns a string literal of the representation of the type or symbol it is applied to. The mangling of types and symbols with D linkage is defined by Name Mangling.

Implementation Defined:

1. whether a leading underscore is added to a symbol

2. the mangling of types and symbols with non-D linkage. For C and C++ linkage, this will typically match what the associated C or C++ compiler does.

.classinfo Property

.classinfo provides information about the dynamic type of a class object. It returns a reference to type object.TypeInfo_Class.

.classinfo applied to an interface gives the information for the interface, not the class it might be an instance of.

.tupleof Property

.tupleof provides a symbol sequence of all the non-static fields in a struct or class. The order of the fields in the tuple matches the order in which the fields are declared. For classes, hidden fields and fields of the bass class are excluded. A common use case is to iterate over the fields with a foreach loop.

struct Vector3
{
int x, y, z;
}

void test()
{
Vector3 v = Vector3(5, 10, 15);
float sum = 0;
foreach(num; v.tupleof)
{
sum+= num;
}
writeln(sum); //30
}

User-Defined Properties

User-defined properties can be created using Property Functions.

Attributes

Grammar

AttributeSpecifier:
Attribute :
Attribute DeclarationBlock

Attribute:
AlignAttribute
AtAttribute
DeprecatedAttribute
FunctionAttributeKwd
LinkageAttribute
Pragma
VisibilityAttribute
abstract
auto
const
final
__gshared
extern
immutable
inout
override
ref
return
__rvalue
scope
shared
static
synchronized

FunctionAttributeKwd:
nothrow
pure

AtAttribute:
@ disable
@ __future
@ 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;
...
}

Function attributes @nogc, nothrow and pure do not propagate inside aggregate declarations.

const pure @safe
{
int i; // function attributes ignored for non-functions
void f(); // const attribute ignored for free function

struct S
{
int i;
void f(); // pure ignored inside struct
}
}
static assert(is(typeof(i) == const int));
static assert(is(typeof(&f) == void function() pure @safe));
static assert(is(typeof(S.i) == const int));
static assert(is(typeof(&S().f) == void delegate() const @safe));

See also: core.attribute for other compiler-recognized attributes.

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 conventions

Note 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 ( default )
align
align ( AssignExpression )

Specifies the alignment of:

1. variables

2. struct fields

3. union fields

4. class fields

5. struct, union, and class types

align(default) (re)sets it to the default, which matches the default member alignment of the companion C compiler. align by itself is the same as align(default).

struct S
{
align(default): // same as `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
}

Undefined Behavior: If any pointers and references to GC allocated objects are not aligned on size_t byte boundaries.

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.

@__future attribute

The @__future attribute is used to mark a new symbol which could cause a name clash in another module. The other module using a symbol with the same name will continue to compile as before but will show a deprecation message. The message indicates that the new symbol will break the existing code in future, and the other module should be updated.

Note: Currently only methods that implicitly override a @__future base class method show a deprecation message.

See DIP1007 for details.

Visibility Attributes

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 ancestors. 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:

- immutable storage class

- immutable type qualifier

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 int x;
@disable void foo();

void main()
{
x++;   // error, x is disabled
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 Attributes

See Function Safety.

System Variables

Variables marked @system cannot be accessed from @safe code.

@system int* p;

struct S
{
@system int i;
}

void main() @safe
{
int x = *p; // error with `-preview=systemVariables`, deprecation otherwise

S s;
s.i = 0; // ditto
}

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

- Return Ref Parameters.

- Return Scope Parameters.

__rvalue Attribute

- RvalueExpression.

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()
{
// scope static int* x;  // cannot be both scope and static
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

Note: escape analysis is only done in @safe code. The -preview=dip1000 switch must also be enabled.

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() @safe
{
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:

- A list of one or more TemplateArguments

- A compile-time expression (matching the above grammar)

- A symbol identifier

- An expression (matching the above grammar) to invoke with an argument list at compile-time

@3 int a; // value attribute
@("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 attribute
@Bar(3) int e; // type instance attribute 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. Parameters do not inherit UDAs from outside the parameter list.

@2 void f(@3 int p)
{
pragma(msg, __traits(getAttributes, p)); // prints AliasSeq!(3)
}

__traits(getAttributes)

UDAs can be extracted into a compile-time sequence using __traits:

@('c') string s;
pragma(msg, __traits(getAttributes, s)); // prints AliasSeq!('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 AliasSeq!(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 AliasSeq!()
pragma(msg, __traits(getAttributes, typeof(a))); // prints AliasSeq!("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 symbols 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 AliasSeq!("foo")
pragma(msg, __traits(getAttributes, Outer!int.S.x));
// prints AliasSeq!()
pragma(msg, __traits(getAttributes, Outer!int.y));
// prints AliasSeq!("foo")
pragma(msg, __traits(getAttributes, Outer!int.fun));
// prints AliasSeq!("foo")
pragma(msg, __traits(getAttributes, Outer!int.Inner));
// prints AliasSeq!("foo", "bar")
pragma(msg, __traits(getAttributes, Outer!int.Inner!int.z));
// prints AliasSeq!("foo", "bar")

UDAs cannot be attached to template parameters.

Pragmas

PragmaDeclaration:
Pragma ;
Pragma DeclarationBlock

PragmaStatement:
Pragma ;
Pragma NoScopeStatement

Pragma:
pragma ( Identifier )
pragma ( Identifier , ArgumentList )

Pragmas pass special information to the implementation and can add vendor specific extensions. Pragmas can be used by themselves terminated with a ;, and can apply to a statement, a block of statements, a declaration, or a block of declarations.

Pragmas can be either a PragmaDeclaration or a PragmaStatement.

pragma(ident);        // just by itself

pragma(ident) declaration; // influence one declaration

pragma(ident): // influence subsequent declarations
declaration;
declaration;

pragma(ident)   // influence block of declarations
{
declaration;
declaration;
}

pragma(ident) statement; // influence one statement

pragma(ident)   // influence block of statements
{
statement;
statement;
}

The kind of pragma it is determined by the Identifier. ArgumentList is a comma-separated list of AssignExpressions. The AssignExpressions must be parsable as expressions, but their meaning is up to the individual pragma semantics.

Predefined Pragmas

All implementations must support these, even if by just ignoring them:

- pragma crt_constructor

- pragma crt_destructor

- pragma inline

- pragma lib

- pragma linkerDirective

- pragma mangle

- pragma msg

- pragma printf

- pragma scanf

- pragma startaddress

Implementation Defined: An implementation may ignore these pragmas.

pragma crt_constructor

Annotates a function so it is run after the C runtime library is initialized and before the D runtime library is initialized.

The function must:

1. be extern (C)

2. not have any parameters

3. not be a non-static member function

4. be a function definition, not a declaration (i.e. it must have a function body)

5. not return a type that has a destructor

6. not be a nested function

__gshared int initCount;

pragma(crt_constructor)
extern(C) void initializer() { initCount += 1; }

No arguments to the pragma are allowed.

A function may be annotated with both pragma(crt_constructor) and pragma(crt_destructor).

Annotating declarations other than function definitions has no effect.

Annotating a struct or class definition does not affect the members of the aggregate.

A function that is annotated with pragma(crt_constructor) may initialize const or immutable variables.

Best Practices: Use for system programming and interfacing with C/C++, for example to allow for initialization of the runtime when loading a DSO, or as a simple replacement for shared static this in betterC mode.

Implementation Defined: The order in which functions annotated with pragma(crt_constructor) are run is implementation defined.

Best Practices: to control the order in which the functions are called within one module, write a single function that calls them in the desired order, and only annotate that function.

Implementation Defined: This uses the mechanism C compilers use to run code before main() is called. C++ compilers use it to run static constructors and destructors. For example, GCC's __attribute__((constructor)) is equivalent. Digital Mars C uses _STI and _STD identifier prefixes to mark crt_constructor and crt_destructor functions.

Implementation Defined: A reference to the annotated function will be inserted in the .init_array section for Elf systems, the XI section for Win32 OMF systems, the .CRT$XCU section for Windows MSCOFF systems, and the __mod_init_func section for OSX systems.

Note: crt_constructor and crt_destructor were implemented in v2.078.0 (2018-01-01). Some compilers exposed non-standard, compiler-specific mechanism before.

pragma crt_destructor

pragma(crt_destructor) works the same as pragma(crt_constructor) except:

1. Annotates a function so it is run after the D runtime library is terminated and before the C runtime library is terminated. Calling C's exit() function also causes the annotated functions to run.

2. The order in which the annotated functions are run is the reverse of those functions annotated with pragma(crt_constructor).

Implementation Defined: This uses the mechanism C compilers use to run code after main() returns or exit() is called. C++ compilers use it to run static destructors. For example, GCC's __attribute__((destructor)) is equivalent. Digital Mars C uses _STI and _STD identifier prefixes to mark crt_constructor and crt_destructor functions.

Implementation Defined: A reference to the annotated function will be inserted in the .fini_array section for Elf systems, the XC section for Win32 OMF systems, the .CRT$XPU section for Windows MSCOFF systems, and the __mod_term_func section for OSX systems.

__gshared int initCount;

pragma(crt_constructor)
extern(C) void initialize() { initCount += 1; }

pragma(crt_destructor)
extern(C) void deinitialize() { initCount -= 1; }

pragma(crt_constructor)
pragma(crt_destructor)
extern(C) void innuendo() { printf("Inside a constructor... Or destructor?\n"); }

pragma inline

Affects whether functions are inlined or not. If at the declaration level, it affects the functions declared in the block it controls. If inside a function, it affects the function it is enclosed by.

It takes two forms:

1. pragma(inline)

Sets the behavior to match the implementation's default behavior.

2. pragma(inline, AssignExpression)

The AssignExpression is evaluated and must have a type that can be converted to a boolean. If the result is false the functions are never inlined, otherwise they are always inlined.

More than one AssignExpression is not allowed.

If there are multiple pragma inlines in a function, the lexically last one takes effect.

pragma(inline):
int foo(int x) // foo() is never inlined
{
pragma(inline, true);
++x;
pragma(inline, false); // supercedes the others
return x + 3;
}

Implementation Defined:

1. The default inline behavior is typically selectable with a compiler switch such as -inline.

2. Whether a particular function can be inlined or not is implementation defined.

3. What happens for pragma(inline, true) if the function cannot be inlined. An error message is typical.

pragma lib

There must be one AssignExpression and it must evaluate at compile time to a string literal.

pragma(lib, "foo.lib");

Implementation Defined: The string literal specifies the file name of a library file. This name is inserted into the generated object file, or otherwise passed to the linker, so the linker automatically links in that library.

pragma linkerDirective

There must be one AssignExpression and it must evaluate at compile time to a string literal.

pragma(linkerDirective, "/FAILIFMISMATCH:_ITERATOR_DEBUG_LEVEL=2");

Implementation Defined: The string literal specifies a linker directive to be embedded in the generated object file. Linker directives are only supported for MS-COFF output.

pragma mangle

Overrides the default mangling for a symbol.

For variables and functions there must be one AssignExpression and it must evaluate at compile time to a string literal. For aggregates there may be one or two AssignExpressions, one of which must evaluate at compile time to a string literal and one which must evaluate to a symbol. If that symbol is a TemplateInstance, the aggregate is treated as a template that has the signature and arguments of the TemplateInstance. The identifier of the symbol is used when no string is supplied. Both arguments may be used used when an aggregate's name is a D keyword.

It only applies to function and variable symbols. Other symbols are ignored.

Implementation Defined: On macOS and Win32, an extra underscore (_) is prepended to the string since 2.079, as is done by the C/C++ toolchain. This allows using the same pragma(mangle) for all compatible (POSIX in one case, win64 in another) platforms instead of having to special-case.

Rationale:

- Enables linking to symbol names that D cannot represent.

- Enables linking to a symbol which is a D keyword, since an Identifier cannot be a keyword.

pragma(mangle, "body")
extern(C) void body_func();
pragma(mangle, "function")
extern(C++) struct _function {}
template ScopeClass(C)
{
pragma(mangle, C)
struct ScopeClass { align(__traits(classInstanceAlignment, C)) void[__traits(classInstanceSize, C)] buffer; }
}
extern(C++)
{
class MyClassA(T) {}
void func(ref ScopeClass!(MyClassA!int)); // mangles as MyClass<int>&
}

pragma msg

Each AssignExpression is evaluated at compile time and then all are combined into a message.

pragma(msg, "compiling...", 6, 1.0); // prints "compiling...61.0" at compile time

Implementation Defined: The form the message takes and how it is presented to the user. One way is by printing them to the standard error stream.

Rationale: Analogously to how writeln() performs a role of writing informational messages during runtime, pragma(msg) performs the equivalent role at compile time. For example,

static if (kilroy)
pragma(msg, "Kilroy was here");
else
pragma(msg, "Kilroy got lost");

pragma printf

pragma(printf) specifies that a function declaration is a printf-like function, meaning it is an extern (C) or extern (C++) function with a format parameter accepting a pointer to a 0-terminated char string conforming to the C99 Standard 7.19.6.1, immediately followed by either a ... variadic argument list or a parameter of type va_list as the last parameter.

If the format argument is a string literal, it is verified to be a valid format string per the C99 Standard. If the format parameter is followed by ..., the number and types of the variadic arguments are checked against the format string.

Diagnosed incompatibilities are:

- incompatible sizes which may cause argument misalignment

- deferencing arguments that are not pointers

- insufficient number of arguments

- struct arguments

- array and slice arguments

- non-pointer arguments to s specifier

- non-standard formats

- undefined behavior per C99

Per the C99 Standard, extra arguments are ignored.

Ignored mismatches are:

- sign mismatches, such as printing an int with a %u format

- integral promotion mismatches, where the format specifies a smaller integral type than int or uint, such as printing a short with the %d format rather than %hd

printf("%k\n", value); // error: non-Standard format k
printf("%d\n");        // error: not enough arguments
printf("%d\n", 1, 2);  // ok, extra arguments ignored

Best Practices: In order to use non-Standard printf/scanf formats, an easy workaround is:

const format = "%k\n";
printf(format.ptr, value);  // no error

Best Practices: Most of the errors detected are portability issues. For instance,

string s;
printf("%.*s\n", s.length, s.ptr);
printf("%d\n", s.sizeof);
ulong u;
scanf("%lld%*c\n", &u);

should be replaced with:

string s;
printf("%.*s\n", cast(int) s.length, s.ptr);
printf("%zd\n", s.sizeof);
ulong u;
scanf("%llu%*c\n", &u);

pragma(printf) applied to declarations that are not functions are ignored. In particular, it has no effect on the declaration of a pointer to function type.

pragma scanf

pragma(scanf) specifies that a function declaration is a scanf-like function, meaning it is an extern (C) or extern (C++) function with a format parameter accepting a pointer to a 0-terminated char string conforming to the C99 Standard 7.19.6.2, immediately followed by either a ... variadic argument list or a parameter of type va_list as the last parameter.

If the format argument is a string literal, it is verified to be a valid format string per the C99 Standard. If the format parameter is followed by ..., the number and types of the variadic arguments are checked against the format string.

Diagnosed incompatibilities are:

- argument is not a pointer to the format specified type

- insufficient number of arguments

- non-standard formats

- undefined behavior per C99

Per the C99 Standard, extra arguments are ignored.

pragma(scanf) applied to declarations that are not functions are ignored. In particular, it has no effect on the declaration of a pointer to function type.

pragma startaddress

There must be one AssignExpression and it must evaluate at compile time to a function symbol.

Implementation Defined: The function symbol specifies the start address for the program. The symbol is inserted into the object file or is otherwise presented to the linker to set the start address. This is not normally used for application level programming, but is for specialized systems work. For applications code, the start address is taken care of by the runtime library.

void foo() { ... }
pragma(startaddress, foo);

Vendor Specific Pragmas

Vendor specific pragma Identifiers can be defined if they are prefixed by the vendor's trademarked name, in a similar manner to version identifiers:

pragma(DigitalMars_extension) { ... }

Implementations must diagnose an error for unrecognized Pragmas, even if they are vendor specific ones.

Implementation Defined: Vendor specific pragmas.

Best Practices: vendor specific pragmas should be wrapped in version statements

version (DigitalMars)
{
pragma(DigitalMars_extension)
{   ... }
}

Expressions

Expressions

Expression:
CommaExpression

An expression is a sequence of operators and operands that specifies an evaluation. The syntax, order of evaluation, and semantics of expressions are 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.

Definitions and Terms

Full Expression

For any expression expr, the full expression of expr is defined as follows. If expr parses as a subexpression of another expression expr_1, then the full expression of expr is the full expression of expr_1. Otherwise, expr is its own full expression.

Each expression has a unique full expression. Example:

return f() + g() * 2;

The full expression of g() * 2 above is f() + g() * 2, but not the full expression of f() + g() because the latter is not parsed as a subexpression.

Note: Although the definition is straightforward, a few subtleties exist related to function literals:

return (() => x + f())() * g();

The full expression of f() above is x + f(), not the expression passed to return. This is because the parent of x + f() has function literal type, not expression type.

Lvalue

The following expressions, and no others, are called lvalue expressions or lvalues:

1. this inside struct and union member functions;

2. a variable, function name, or invocation of a function that returns by reference;

3. the result of the . PostfixExpression and Module Scope Operator when the rightmost side of the dot is a variable, field (direct or static), function name, or invocation of a function that returns by reference;

4. the result of the following expressions:

- built-in unary operators + (when applied to an lvalue), *, ++ (prefix only), -- (prefix only);

- built-in indexing operator [] (but not the slicing operator);

- built-in assignment operators, i.e. =, +=, *=, /=, %=, &=, |=, ^=, ~=, <<=, >>=, >>>=, and ^^=;

- user-defined operators if and only if the function called as a result of lowering returns by reference;

- the ConditionalExpression operator e ? e_1 : e_2 under the following circumstances:

1. e_1 and e_2 are lvalues of the same type; OR

2. One of e_1 and e_2 is an lvalue of type T and the other has an alias this which converts it to an lvalue of T;

- mixin expressions if and only if the compilation of the expression resulting from compiling the argument(s) to mixin is an lvalue;

- cast(U) expressions applied to lvalues of type T when T* is implicitly convertible to U*;

- cast(TypeCtorsopt) when applied to an lvalue.

Rvalue

Expressions that are not lvalues are rvalues. Rvalues include all literals, special value keywords such as __FILE__ and __LINE__, enum values, and the result of expressions not defined as lvalues above.

The built-in address-of operator (unary &) may only be applied to lvalues.

Smallest Short-Circuit Expression

Given an expression expr that is a subexpression of a full expression fullexpr, the smallest short-circuit expression, if any, is the shortest subexpression scexpr of fullexpr that is an AndAndExpression (&&) or an OrOrExpression (||), such that expr is a subexpression of scexpr. Example:

((f() * 2 && g()) + 1) || h()

The smallest short-circuit expression of the subexpression f() * 2 above is f() * 2 && g(). Example:

(f() && g()) + h()

The subexpression h() above has no smallest short-circuit expression.

Order Of Evaluation

Best Practices: Even when the order of evaluation is well-defined, writing code that depends on it is rarely recommended.

Increment and Decrement

Built-in prefix unary expressions ++ and -- are evaluated as if lowered (rewritten) to assignments as follows:

Expression

Equivalent

++expr

((expr) += 1)

--expr

((expr) -= 1)

Therefore, the result of prefix ++ and -- is the lvalue after the side effect has been effected.

Built-in postfix unary expressions ++ and -- are evaluated as if lowered (rewritten) to lambda invocations as follows:

Expression

Equivalent

expr++

(ref x){auto t = x; ++x; return t;}(expr)

expr--

(ref x){auto t = x; --x; return t;}(expr)

Therefore, the result of postfix ++ and -- is an rvalue just before the side effect has been effected.

int i = 0;
assert(++i == 1);
assert(i++ == 1);
assert(i == 2);

int* p = [1, 2].ptr;
assert(*p++ == 1);
assert(*p == 2);

Binary Expressions

Binary expressions except for AssignExpression, OrOrExpression, and AndAndExpression are evaluated in lexical order (left-to-right). Example:

int i = 2;
i = ++i * i++ + i;
assert(i == 3 * 3 + 4);

OrOrExpression and AndAndExpression evaluate their left-hand side argument first. Then, OrOrExpression evaluates its right-hand side if and only if its left-hand side does not evaluate to nonzero. AndAndExpression evaluates its right-hand side if and only if its left-hand side evaluates to nonzero.

Implementation Defined: The order of evaluation of the operands of AssignExpression.

Conditional Expressions

ConditionalExpression evaluates its left-hand side argument first. Then, if the result is nonzero, the second operand is evaluated. Otherwise, the third operand is evaluated.

Function Calls

Calls to functions with extern(D) linkage (which is the default linkage) are evaluated in the following order:

1. If necessary, the address of the function to call is evaluated (e.g. in the case of a computed function pointer or delegate).

2. Arguments are evaluated left to right.

3. Transfer of execution is passed to the function.

Example calling a function pointer:

void function(int a, int b, int c) fun()
{
writeln("fun() called");
static void r(int a, int b, int c) { writeln("callee called"); }
return &r;
}
int f1() { writeln("f1() called"); return 1; }
int f2() { writeln("f2() called"); return 2; }
int f3(int x) { writeln("f3() called"); return x + 3; }
int f4() { writeln("f4() called"); return 4; }

// evaluates fun() then f1() then f2() then f3() then f4()
// after which control is transferred to the callee
fun()(f1(), f3(f2()), f4());

Implementation Defined: The order of evaluation of function arguments for functions with linkage other than extern(D).

Lifetime of Temporaries

Expressions and statements may create and/or consume rvalues. Such values are called temporaries and do not have a name or a visible scope. Their lifetime is managed automatically as defined in this section.

For each evaluation that yields a temporary value, the lifetime of that temporary begins at the evaluation point, similarly to creation of a usual named value initialized with an expression.

Termination of lifetime of temporaries does not obey the customary scoping rules and is defined as follows:

- If:

1. the full expression has a smallest short-circuit expression expr; and

2. the temporary is created on the right-hand side of the && or || operator; and

3. the right-hand side is evaluated,

then temporary destructors are evaluated right after the right-hand side expression has been evaluated and converted to bool. Evaluation of destructors proceeds in reverse order of construction.

- For all other cases, the temporaries generated for the purpose of invoking functions are deferred to the end of the full expression. The order of destruction is inverse to the order of construction.

If a subexpression of an expression throws an exception, all temporaries created up to the evaluation of that subexpression will be destroyed per the rules above. No destructor calls will be issued for temporaries not yet constructed.

Note: An intuition behind these rules is that destructors of temporaries are deferred to the end of full expression and in reverse order of construction, with the exception that the right-hand side of && and || are considered their own full expressions even when part of larger expressions.

Note: The ConditionalExpression e_1 ? e_2 : e_3 is not a special case although it evaluates expressions conditionally: e_1 and one of e_2 and e_3 may create temporaries. Their destructors are inserted to the end of the full expression in the reverse order of creation.

Example:

import std.stdio;

struct S
{
int x;
this(int n) { x = n; writefln("S(%s)", x); }
~this() { writefln("~S(%s)", x); }
}

void main()
{
bool b = (S(1) == S(2) || S(3) != S(4)) && S(5) == S(6);
}

The output of the code above is:

S(1)
S(2)
S(3)
S(4)
~S(4)
~S(3)
S(5)
S(6)
~S(6)
~S(5)
~S(2)
~S(1)

First, S(1) and S(2) are evaluated in lexical order. Per the rules, they will be destroyed at the end of the full expression and in reverse order. The comparison S(1) == S(2) yields false, so the right-hand side of the || is evaluated causing S(3) and S(4) to be evaluated, also in lexical order. However, their destruction is not deferred to the end of the full expression. Instead, S(4) and then S(3) are destroyed at the end of the || expression. Following their destruction, S(5) and S(6) are constructed in lexical order. Again they are not destroyed at the end of the full expression, but right at the end of the && expression. Consequently, the destruction of S(6) and S(5) is carried before that of S(2) and S(1).
Comma Expression

CommaExpression:
AssignExpression
CommaExpression , AssignExpression

The left operand of the , is evaluated, then the right operand is evaluated. In C, the result of a comma expression is the result of the right operand. In D, using the result of a comma expression isn't allowed. Consequently a comma expression is only useful when each operand has a side effect.

int x, y;
// expression statement
x = 1, y = 1;
// evaluate a comma expression at the end of each loop iteration
for (; y < 10; x++, y *= 2)
writefln("%s, %s", x, y);

Rationale: The comma expression has been used unintentionally, either by bracket nesting mistakes or when users expect a sequence of arguments instead of a single expression. Those bugs can be hard to detect in code review. Disallowing use of the result turns those bugs into errors.

Assign Expressions

AssignExpression:
ConditionalExpression
ConditionalExpression = AssignExpression
ConditionalExpression += AssignExpression
ConditionalExpression -= AssignExpression
ConditionalExpression *= AssignExpression
ConditionalExpression /= AssignExpression
ConditionalExpression %= AssignExpression
ConditionalExpression &= AssignExpression
ConditionalExpression |= AssignExpression
ConditionalExpression ^= AssignExpression
ConditionalExpression ~= AssignExpression
ConditionalExpression <<= AssignExpression
ConditionalExpression >>= AssignExpression
ConditionalExpression >>>= AssignExpression
ConditionalExpression ^^= AssignExpression

For all assign expressions, the left operand must be a modifiable lvalue. The type of the assign expression is the type of the left operand, and the result is the value of the left operand after assignment occurs. The resulting expression is a modifiable lvalue.

Undefined Behavior: If either operand is a reference type and one of the following:

1. the operands have partially overlapping storage

2. the operands' storage overlaps exactly but the types are different

Implementation Defined: If neither operand is a reference type and one of the following:

1. the operands have partially overlapping storage

2. the operands' storage overlaps exactly but the types are different

Simple Assignment Expression

If the operator is = then it is simple assignment.

- If the left operand is a struct that defines opAssign, the behaviour is defined by the overloaded function.

- If the left and right operands are of the same struct type, and the struct type has a Postblit, then the copy operation is as described in Struct Postblit.

- If the lvalue is the .length property of a dynamic array, the behavior is as described in Setting Dynamic Array Length.

- If the left operand is a slice expression, the behavior is as described in Array Copying and Array Filling.

- If the lvalue is an array, the behavior is as described in Array Assignment.

- If the lvalue is a user-defined property, the behavior is as described in Property Functions.

Otherwise, the right operand is implicitly converted to the type of the left operand, and assigned to it.

Assignment Operator Expressions

For arguments of built-in types, assignment operator expressions such as

a op= b

are semantically equivalent to:

a = cast(typeof(a))(a op b)

except that:

- operand a is only evaluated once,

- overloading op uses a different function than overloading op= does, and

- the left operand of >>>= does not undergo Integer Promotions before shifting.

Narrowing conversions are allowed. Truncating conversions will be an error.

void f(short s)
{
byte b;
b += s; // OK, though it may overflow
//b += 1.5F; // Deprecated, truncation
}

For user-defined types, assignment operator expressions are overloaded separately from the binary operators. Still the left operand must be an lvalue.

Conditional Expressions

ConditionalExpression:
OrOrExpression
OrOrExpression ? Expression : ConditionalExpression

The first expression is converted to bool, and is evaluated.

If it is true, then the second expression is evaluated, and its result is the result of the conditional expression.

If it is false, then the third expression is evaluated, and its result is the result of the conditional expression.

If either the second or third expressions are of type void, then the resulting type is void. Otherwise, the second and third expressions are implicitly converted to a common type which becomes the result type of the conditional expression.

Note: When a conditional expression is the left operand of an assign expression, parentheses are required for disambiguation:

bool test;
int a, b, c;
...
test ? a = b : c = 2;   // error
(test ? a = b : c) = 2; // OK

This makes the intent clearer, because the first statement can easily be misread as the following code:

test ? a = b : (c = 2);

Logical Expressions

See Also:
UnaryExpression for !expr.

OrOr Expressions

OrOrExpression:
AndAndExpression
OrOrExpression || AndAndExpression

The result type of an OrOrExpression is bool, unless the right operand has type void, when the result is type void.

The OrOrExpression evaluates its left operand.

If the left operand, converted to type bool, evaluates to true, then the right operand is not evaluated. If the result type of the OrOrExpression is bool then the result of the expression is true.

If the left operand is false, then the right operand is evaluated. If the result type of the OrOrExpression is bool then the result of the expression is the right operand converted to type bool.

AndAnd Expressions

AndAndExpression:
OrExpression
AndAndExpression && OrExpression

The result type of an AndAndExpression is bool, unless the right operand has type void, when the result is type void.

The AndAndExpression evaluates its left operand.

If the left operand, converted to type bool, evaluates to false, then the right operand is not evaluated. If the result type of the AndAndExpression is bool then the result of the expression is false.

If the left operand is true, then the right operand is evaluated. If the result type of the AndAndExpression is bool then the result of the expression is the right operand converted to type bool.

Bitwise Expressions

Bit wise expressions perform a bitwise operation on their operands. Their operands must be integral types. First, the Usual Arithmetic Conversions are done. Then, the bitwise operation is done.

See Also:
ShiftExpression, ComplementExpression

Note: If an OrExpression, XorExpression or AndExpression appears on either side of an EqualExpression, IdentityExpression or RelExpression, it is a compile error. Instead, disambiguate by using parentheses.

int x, a, b;
x = a & 5 == b; // error
x = a & 5 is b; // error
x = a & 5 <= b; // error

x = (a & 5) == b; // OK
x = a & (5 == b); // OK

Or Expressions

OrExpression:
XorExpression
OrExpression | XorExpression

The operands are OR'd together.

Xor Expressions

XorExpression:
AndExpression
XorExpression ^ AndExpression

The operands are XOR'd together.

And Expressions

AndExpression:
CmpExpression
AndExpression & CmpExpression

The operands are AND'd together.

Compare Expressions

CmpExpression:
EqualExpression
IdentityExpression
RelExpression
InExpression
ShiftExpression

Equality Expressions

EqualExpression:
ShiftExpression == ShiftExpression
ShiftExpression != ShiftExpression

Equality expressions compare the two operands for equality (==) or inequality (!=). The type of the result is bool.

Inequality is defined as the logical negation of equality.

- If the operands are integral values, the Usual Arithmetic Conversions are applied to bring them to a common type before comparison. Equality is defined as the bit patterns of the common type match exactly.

- If the operands are pointers, equality is defined as the bit patterns of the operands match exactly. Both types must match, or one can be typeof(null).

- For float, double, and real values, the Usual Arithmetic Conversions are applied to bring them to a common type before comparison. The values -0 and +0 are considered equal. If either or both operands are NaN, then == returns false and != returns true. Otherwise, the bit patterns of the common type are compared for equality.

- For static and dynamic arrays, equality is defined as the lengths of the arrays matching, and the elements in each compare equal. The element types must have a common type.

assert(5 == 5L);
assert(byte(4) == 4F);

int i = 1, j = 1;
assert(&i != &j);
assert(&i != null);

// elements of different types are comparable, even when different sizes
int[] ia = ['A', 'B', 'C'];
assert(ia == "ABC");
byte[] ba = [1, 2];
assert(ba == [1F, 2F]);

Deprecated:
For complex numbers, equality is defined as equivalent to:

x.re == y.re && x.im == y.im

Class & Struct Equality

For class references, a == b is rewritten to .object.opEquals(a, b), which handles null. This is intended to compare the contents of two objects, however an appropriate opEquals method override must be defined for this to work. The default opEquals provided by the root Object class is equivalent to the is operator.

For struct objects, the expression (a == b) is rewritten as a.opEquals(b), or failing that, b.opEquals(a).

For both class references and struct objects, (a != b) is rewritten as !(a == b).

See opEquals for details.

Struct Equality

For struct objects, equality means the result of the opEquals() member function. If an opEquals() is not provided, one will be generated. Equality is defined as the logical product of all equality results of the corresponding object fields.

struct S
{
int i = 4;
string s = "four";
}

S s;
assert(s == S());
s.s = "foul";
assert(s != S());

Implementation Defined: The contents of any alignment gaps in the struct object.

If there are overlapping fields, which happens with unions, the default equality will compare each of the overlapping fields.

Best Practices: An opEquals() can account for which of the overlapping fields contains valid data. An opEquals() can override the default behavior of floating point NaN values always comparing as unequal. Be careful using memcmp() to implement opEquals() if:

- there are any alignment gaps

- any fields have an opEquals()

- there are any floating point fields that may contain NaN or -0 values

Identity Expressions

IdentityExpression:
ShiftExpression is ShiftExpression
ShiftExpression ! is ShiftExpression

The is operator compares for identity of expression values. To compare for nonidentity, use e1 !is e2. The type of the result is bool. The operands undergo the Usual Arithmetic Conversions to bring them to a common type before comparison.

For class / interface objects, identity is defined as the object references being identical. Class references can be efficiently compared against null using is. Note that interface objects need not have the same reference of the class they were cast from. To test whether an interface shares a class instance with another interface / class value, cast both operands to Object before comparing with is.

interface I { void g(); }
interface I1 : I { void g1(); }
interface I2 : I { void g2(); }
interface J : I1, I2 { void h(); }

class C : J
{
override void g() { }
override void g1() { }
override void g2() { }
override void h() { }
}

void main() @safe
{
C c = new C;
I i1 = cast(I1) c;
I i2 = cast(I2) c;
assert(i1 !is i2); // not identical
assert(c !is i2); // not identical
assert(cast(Object) i1 is cast(Object) i2); // identical
}

For struct objects and floating point values, identity is defined as the bits in the operands being identical.

For static and dynamic arrays, identity of two arrays is given when both arrays refer to the same memory location and contain the same number of elements.

Object o;
assert(o is null);

auto a = [1, 2];
assert(a is a[0..$]);
assert(a !is a[0..1]);

auto b = [1, 2];
assert(a !is b);

Deprecated:
Use of is to compare static arrays by address and length is deprecated. To do so, use the slice operator and compare slices of the arrays instead; for example, a1[] is a2[].

For other operand types, identity is defined as being the same as equality.

The identity operator is cannot be overloaded.

Relational Expressions

RelExpression:
ShiftExpression < ShiftExpression
ShiftExpression <= ShiftExpression
ShiftExpression > ShiftExpression
ShiftExpression >= ShiftExpression

First, the Usual Arithmetic Conversions are done on the operands. The result type of a relational expression is bool.

Array Comparisons

For static and dynamic arrays, the result of a CmpExpression is the result of the operator applied to the first non-equal element of the array. If two arrays compare equal, but are of different lengths, the shorter array compares as "less" than the longer array.

Integer Comparisons

Integer comparisons happen when both operands are integral types.

Integer comparison operators

Operator

Relation

<

less

>

greater

<=

less or equal

>=

greater or equal

==

equal

!=

not equal

It is an error to have one operand be signed and the other unsigned for a <, <=, > or >= expression. Use casts to make both operands signed or both operands unsigned.

Floating Point Comparisons

If one or both operands are floating point, then a floating point comparison is performed.

A CmpExpression can have NaN operands. If either or both operands is NaN, the floating point comparison operation returns as follows:

Floating point comparison operators

Operator

Relation

Returns

<

less

false

>

greater

false

<=

less or equal

false

>=

greater or equal

false

==

equal

false

!=

unordered, less, or greater

true

Best Practices: Although IdentityExpression can be used to check for T.nan, there are other floating-point values for NaN produced at runtime. Use std.math.traits.isNaN to handle all of them.

Class and Struct Comparisons

For struct objects, a RelExpression performs a comparison which first evaluates a matching opCmp method call.

For class references, a RelExpression performs a comparison which first evaluates to an int which is either:

- 0 if the two object references are identical

- -1 if the left-hand expression is null

- 1 if the right-hand expression is null

- the result of a matching opCmp call

class C
{
override int opCmp(Object o) { assert(0); }
}

void main()
{
C c;
//if (c < null) {}  // compile-time error
assert(c is null);
assert(c < new C); // C.opCmp is not called
}

Secondly, for class and struct objects, the evaluated int is compared against zero using the given operator, which forms the result of the RelExpression. For more information, see opCmp.

In Expressions

InExpression:
ShiftExpression in ShiftExpression
ShiftExpression ! in ShiftExpression

A container such as an associative array can be tested to see if it contains a certain key:

int[string] foo;
...
if ("hello" in foo)
{
// the string was found
}

The result of an InExpression is a pointer for associative arrays. The pointer is null if the container has no matching key. If there is a match, the pointer points to a value associated with the key.

The !in expression is the logical negation of the in operation.

The in expression has the same precedence as the relational expressions <, <=, etc.

Note: When overloading in, normally only opBinaryRight would be defined. This is because the operation is usually not defined by the key type but by the container, which appears on the right hand side of the in operator.

Shift Expressions

ShiftExpression:
AddExpression
ShiftExpression << AddExpression
ShiftExpression >> AddExpression
ShiftExpression >>> AddExpression

The operands must be integral types, and undergo the Integer Promotions. The result type is the type of the left operand after the promotions. The result value is the result of shifting the bits by the right operand's value.

- << is a left shift.

- >> is a signed right shift.

- >>> is an unsigned right shift.

Implementation Defined: The result of a shift by a negative value or by the same or more bits than the size of the quantity being shifted is undefined. When the shift amount is known at compile time, doing this results in a compile error.

int c;

int s = -3;
auto y = c << s; // implementation defined value

auto x = c << 33;  // error, max shift count allowed is 31

Additive Expressions

AddExpression:
MulExpression
AddExpression + MulExpression
AddExpression - MulExpression
AddExpression ~ MulExpression

Add Expressions

In the cases of the Additive operations + and -:

If the operands are of integral types, they undergo the Usual Arithmetic Conversions, and then are brought to a common type using the Usual Arithmetic Conversions.

If both operands are of integral types and an overflow or underflow occurs in the computation, wrapping will happen. For example:

- uint.max + 1 == uint.min

- uint.min - 1 == uint.max

- int.max + 1 == int.min

- int.min - 1 == int.max

If either operand is a floating point type, the other is implicitly converted to floating point and they are brought to a common type via the Usual Arithmetic Conversions.

Add expressions for floating point operands are not associative.

Pointer Arithmetic

If the first operand is a pointer, and the second is an integral type, the resulting type is the type of the first operand, and the resulting value is the pointer plus (or minus) the second operand multiplied by the size of the type pointed to by the first operand.

int[] a = [1,2,3];
int* p = a.ptr;
assert(*p == 1);

*(p + 2) = 4; // same as `p[2] = 4`
assert(a[2] == 4);

IndexOperation can also be used with a pointer and has the same behaviour as adding an integer, then dereferencing the result.

If the second operand is a pointer, and the first is an integral type, and the operator is +, the operands are reversed and the pointer arithmetic just described is applied.

Producing a pointer through pointer arithmetic is not allowed in @safe code.

If both operands are pointers, and the operator is +, then it is illegal.

If both operands are pointers, and the operator is -, the pointers are subtracted and the result is divided by the size of the type pointed to by the operands. In this calculation the assumed size of void is one byte. It is an error if the pointers point to different types. The type of the result is ptrdiff_t.

int[] a = [1,2,3];
ptrdiff_t d = &a[2] - a.ptr;
assert(d == 2);

Cat Expressions

In the case of the Additive operation ~:

A CatExpression concatenates a container's data with other data, producing a new container.

For a dynamic array, the other operand must either be another array or a single value that implicitly converts to the element type of the array. See Array Concatenation.

Mul Expressions

MulExpression:
UnaryExpression
MulExpression * UnaryExpression
MulExpression / UnaryExpression
MulExpression % UnaryExpression

The operands must be arithmetic types. They undergo the Usual Arithmetic Conversions.

For integral operands, the *, /, and % correspond to multiply, divide, and modulus operations. For multiply, overflows are ignored and simply chopped to fit into the integral type.

Division

For integral operands of the / and % operators, the quotient rounds towards zero and the remainder has the same sign as the dividend.

The following divide or modulus integral operands:

- denominator is 0

- signed int.min is the numerator and -1 is the denominator

- signed long.min is the numerator and -1L is the denominator

are illegal if encountered during Compile Time Execution.

Undefined Behavior: is exhibited if they are encountered during run time. core.checkedint can be used to check for them and select a defined behavior.

Floating Point

For floating point operands, the * and / operations correspond to the IEEE 754 floating point equivalents. % is not the same as the IEEE 754 remainder. For example, 15.0 % 10.0 == 5.0, whereas for IEEE 754, remainder(15.0,10.0) == -5.0.

Mul expressions for floating point operands are not associative.

Unary Expressions

UnaryExpression:
& UnaryExpression
++ UnaryExpression
-- UnaryExpression
* UnaryExpression
- UnaryExpression
+ UnaryExpression
! UnaryExpression
ComplementExpression
DeleteExpression
CastExpression
ThrowExpression
PowExpression

Operator

Description

&

Take memory address of an lvalue - see pointers

++

Increment before use - see order of evaluation

--

Decrement before use

*

Dereference/indirection - typically for pointers

-

Negative

+

Positive

!

Logical NOT

The usual Integer Promotions are performed prior to unary - and + operations.

Complement Expressions

ComplementExpression:
~ UnaryExpression

ComplementExpressions work on integral types (except bool). All the bits in the value are complemented. The usual Integer Promotions are performed prior to the complement operation.

Delete Expressions

DeleteExpression:
delete UnaryExpression

Deprecated:
delete has been deprecated. Instead, please use destroy if feasible, or core.memory.__delete as a last resort.

If the UnaryExpression is a class object reference, and there is a destructor for that class, the destructor is called for that object instance.

Next, if the UnaryExpression is a class object reference, or a pointer to a struct instance, and the class or struct has overloaded operator delete, then that operator delete is called for that class object instance or struct instance.

Otherwise, the garbage collector is called to immediately free the memory allocated for the class instance or struct instance.

If the UnaryExpression is a pointer or a dynamic array, the garbage collector is called to immediately release the memory.

The pointer, dynamic array, or reference is set to null after the delete is performed. Any attempt to reference the data after the deletion via another reference to it will result in undefined behavior.

If UnaryExpression is a variable allocated on the stack, the class destructor (if any) is called for that instance. The garbage collector is not called.

Undefined Behavior:

1. Using delete to free memory not allocated by the garbage collector.

2. Referring to data that has been the operand of delete.

Cast Expressions

CastExpression:
cast ( Type ) UnaryExpression
CastQual

A CastExpression converts the UnaryExpression to Type.

cast(foo) -p; // cast (-p) to type foo
(foo) - p;      // subtract p from foo

Basic Data Types

For situations where implicit conversions on basic types cannot be performed, the type system may be forced to accept the reinterpretation of a memory region by using a cast.

An example of such a scenario is represented by trying to store a wider type into a narrower one:

int a;
byte b = a; // cannot implicitly convert expression a of type int to byte

When casting a source type that is wider than the destination type, the value is truncated to the destination size.

int a = 64389; // 00000000 00000000 11111011 10000101
byte b = cast(byte) a;       // 10000101
ubyte c = cast(ubyte) a;     // 10000101
short d = cast(short) a;     // 11111011 10000101
ushort e = cast(ushort) a;   // 11111011 10000101

writeln(b);
writeln(c);
writeln(d);
writeln(e);

For integral types casting from a narrower type to a wider type is done by performing sign extension.

ubyte a = 133;  // 10000101
byte b = a;     // 10000101

writeln(a);
writeln(b);

ushort c = a;   // 00000000 10000101
short d = b;    // 11111111 10000101

writeln(c);
writeln(d);

See also: Casting Integers.

Class References

Any casting of a class reference to a derived class reference is done with a runtime check to make sure it really is a downcast. null is the result if it isn't.

class A {}
class B : A {}

void main()
{
A a = new A;
//B b = a;         // error, need cast
B b = cast(B) a; // b is null if a is not a B
assert(b is null);

a = b;         // no cast needed
a = cast(A) b; // no runtime check needed for upcast
assert(a is b);
}

In order to determine if an object o is an instance of a class B use a cast:

if (cast(B) o)
{
// o is an instance of B
}
else
{
// o is not an instance of B
}

Casting a pointer type to and from a class type is done as a type paint (i.e. a reinterpret cast).

Pointers

Casting a pointer variable to another pointer type modifies the value that will be obtained as a result of dereferencing, along with the number of bytes on which pointer arithmetic is performed.

int val = 25185; // 00000000 00000000 01100010 01100001
char *ch = cast(char*)(&val);

writeln(*ch);    // a
writeln(cast(int)(*ch)); // 97
writeln(*(ch + 1));  // b
writeln(cast(int)(*(ch + 1)));   // 98

Similarly, when casting a dynamically allocated array to a type of smaller size, the bytes of the initial array will be divided and regrouped according to the new dimension.

import core.stdc.stdlib;

int *p = cast(int*) malloc(5 * int.sizeof);
for (int i = 0; i < 5; i++) {
p[i] = i + 'a';
}
// p = [97, 98, 99, 100, 101]

char* c = cast(char*) p;     // c = [97, 0, 0, 0, 98, 0, 0, 0, 99 ...]
for (int i = 0; i < 5 * int.sizeof; i++) {
writeln(c[i]);
}

When casting a pointer of type A to a pointer of type B and type B is wider than type A, attempts at accessing the memory exceeding the size of A will result in undefined behaviour.

char c = 'a';
int *p = cast(int*) (&c);
writeln(*p);

It is also possible to cast pointers to basic data types. A common practice could be to cast the pointer to an int value and then print its address:

import core.stdc.stdlib;

int *p = cast(int*) malloc(int.sizeof);
int a = cast(int) p;
writeln(a);

Arrays

T[] a;
...
cast(U[]) a

Casting a non-literal dynamic array a to another dynamic array type U[] is allowed only when the result will contain every byte of data that was referenced by a. This is enforced with a runtime check that the byte length of a's elements is divisible by U.sizeof. If there is a remainder, a runtime error is generated. The cast is done as a type paint, and the resulting array's length is set to (a.length * T.sizeof) / U.sizeof.

byte[] a = [1,2,3];
//auto b = cast(int[])a; // runtime error: array cast misalignment

int[] c = [1, 2, 3];
auto d = cast(byte[])c; // ok
// prints:
// [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]
writeln(d);

Undefined Behavior: Casting a non-literal array to bool[] when any element has a byte representation other than 0 or 1.

See also: Casting array literals.

A slice of statically known length can be cast to a static array type when the byte counts of their respective data match.

void f(int[] b)
{
char[4] a;
static assert(!__traits(compiles, a = cast(char[4]) b)); // unknown length
static assert(!__traits(compiles, a = cast(char[4]) b[0..2])); // too many bytes

a = cast(char[4]) b[0..1]; // OK
const i = 1;
a = cast(char[4]) b[i..2]; // OK
}

See also: Slice conversion to static array.

Static Arrays

Casting a static array to another static array is done only if the array lengths multiplied by the element sizes match; a mismatch is illegal. The cast is done as a type paint (aka a reinterpret cast). The contents of the array are not changed.

byte[16] b = 3; // set each element to 3
assert(b[0] == 0x03);
int[4] ia = cast(int[4]) b;
// print elements as hex
foreach (i; ia)
writefln("%x", i);
/* prints:
3030303
3030303
3030303
3030303
*/

Integers

Casting an integer to a smaller integral will truncate the value towards the least significant bits. If the target type is signed and the most significant bit is set after truncation, that bit will be lost from the value and the sign bit will be set.

uint a = 260;
auto b = cast(ubyte) a;
assert(b == 4); // truncated like 260 & 0xff

int c = 128;
assert(cast(byte)c == -128); // reinterpreted

Converting between signed and unsigned types will reinterpret the value if the destination type cannot represent the source value.

short c = -1;
ushort d = c;
assert(d == ushort.max);
assert(uint(c) == uint.max);

ubyte e = 255;
byte f = e;
assert(f == -1); // reinterpreted
assert(short(e) == 255); // no change

Floating Point

Casting a floating point literal from one type to another changes its type, but internally it is retained at full precision for the purposes of constant folding.

void test()
{
real a = 3.40483L;
real b;
b = 3.40483;     // literal is not truncated to double precision
assert(a == b);
assert(a == 3.40483);
assert(a == 3.40483L);
assert(a == 3.40483F);
double d = 3.40483; // truncate literal when assigned to variable
assert(d != a);     // so it is no longer the same
const double x = 3.40483; // assignment to const is not
assert(x == a);     // truncated if the initializer is visible
}

Casting a floating point value to an integral type is the equivalent of converting to an integer using truncation. If the floating point value is outside the range of the integral type, the cast will produce an invalid result (this is also the case in C, C++).

void main()
{
int a = cast(int) 0.8f;
assert(a == 0);
long b = cast(long) 1.5;
assert(b == 1L);
long c = cast(long) -1.5;
assert(c == -1);

// if the float overflows, the cast returns the integer value of
// 80000000_00000000H (64-bit operand) or 80000000H (32-bit operand)
long d = cast(long) float.max;
assert(d == long.min);
int e = cast(int) (1234.5 + int.max);
assert(e == int.min);

// for types represented on 16 or 8 bits, the result is the same as
// 32-bit types, but the most significant bits are ignored
short f = cast(short) float.max;
assert(f == 0);
}

Structs

An expression e can be cast to a struct type S:

- The compiler attempts a PostfixExpression S(e). If that would fail:

- When e is a struct or static array instance, its data is reinterpreted as the target type S. The data sizes must match.

- Otherwise, it is an error.

Note: The following examples assume a LittleEndian byte order.

struct S
{
int i;
}
struct R
{
short[2] a;
}

S s = cast(S) 5; // same as S(5)
assert(s.i == 5);
static assert(!__traits(compiles, cast(S) long.max)); // S(long.max) is invalid

R r = R([1, 2]);
s = cast(S) r; // reinterpret r
assert(s.i == 0x00020001);

byte[4] a = [1, 0, 2, 0];
assert(r == cast(R) a); // reinterpret a

A struct instance can be cast to a static array type when their .sizeof properties each give the same result.

struct S { short a, b, c; }

S s = S(1, 2, 3);
static assert(!__traits(compiles, cast(short[2]) s)); // size mismatch

short[3] x = cast(short[3]) s;
assert(x.tupleof == s.tupleof);

auto y = cast(byte[6]) s;
assert(y == [1, 0, 2, 0, 3, 0]);

Qualifier Cast

CastQual:
cast ( TypeCtorsopt ) UnaryExpression

A CastQual replaces the qualifiers in the type of the UnaryExpression:

shared int x;
static assert(is(typeof(cast(const)x) == const int));

Casting with no type or qualifiers removes any top level const, immutable, shared or inout type modifiers from the type of the UnaryExpression.

shared int x;
static assert(is(typeof(cast()x) == int));

Casting to void

Casting an expression to void type is allowed to mark that the result is unused. On ExpressionStatement, it could be used properly to avoid a "has no effect" error.

void foo(lazy void exp) {}
void main()
{
foo(10);            // NG - expression '10' has no effect
foo(cast(void)10);  // OK
}

Throw Expression

ThrowExpression:
throw AssignExpression

AssignExpression is evaluated and must yield a reference to a Throwable or a class derived from Throwable. The reference is thrown as an exception, interrupting the current control flow to continue at a suitable catch clause of a try-statement. This process will execute any applicable scope (exit) / scope (failure) passed since entering the corresponding try block.

throw new Exception("message");

The Throwable must not be a qualified as immutable, const, inout or shared. The runtime may modify a thrown object (e.g. to contain a stack trace) which would violate const or immutable objects.

A ThrowExpression may be nested in another expression:

void foo(int function() f) {}

void main() {
foo(() => throw new Exception());
}

The type of a ThrowExpression is noreturn.

Best Practices: Use Assert Expressions rather than Error to report program bugs and abort the program.

Pow Expressions

PowExpression:
PostfixExpression
PostfixExpression ^^ UnaryExpression

PowExpression raises its left operand to the power of its right operand.

Postfix Expressions

PostfixExpression:
PrimaryExpression
PostfixExpression . Identifier
PostfixExpression . TemplateInstance
PostfixExpression . NewExpression
PostfixExpression ++
PostfixExpression --
PostfixExpression ( NamedArgumentListopt )
TypeCtorsopt BasicType ( NamedArgumentListopt )
PostfixExpression IndexOperation
PostfixExpression SliceOperation

Operation

Description

. Identifier

Either:

- Access a property of a type or expression.

- Access a member of a module, package, aggregate type or instance, enum or template instance.

- Dereference a pointer to a struct/union instance and access a member of it.

- Call a free function using UFCS.

. NewExpression

Instantiate a nested class

++

Increment after use - see order of evaluation

--

Decrement after use

(args)

Either:

- Call an expression with optional arguments

- Call static opCall on a user-defined type

- Construct a type with optional arguments

IndexOperation

Select a single element

SliceOperation

Select a series of elements

Postfix Argument Lists

ArgumentList:
AssignExpression
AssignExpression ,
AssignExpression , ArgumentList

NamedArgumentList:
NamedArgument
NamedArgument ,
NamedArgument , NamedArgumentList

NamedArgument:
Identifier : AssignExpression
AssignExpression

Callable Expressions

A callable expression can precede a list of named arguments in parentheses. The following expressions can be called:

- A function

- A function pointer

- A delegate

- An aggregate type instance which defines opCall

void f(int, int);

void g()
{
f(5, 6);
(&f)(5, 6);
}

Matching Arguments to Parameters

Arguments in a NamedArgumentList are matched to function parameters as follows:

1. If the first argument has no name, it will be assigned to the first function parameter.

2. A named argument is assigned to a function parameter with the same name. It is an error if no such parameter exists.

3. Any unnamed argument is assigned to the next parameter relative to the preceding argument's parameter. It is an error if no such parameter exists, i.e. when the preceding argument assigns to the last parameter.

4. Assigning a parameter more than once is an error.

5. Not assigning a parameter an argument is also an error, unless the parameter has a Default Argument.

Constructing a Type with an Argument List

A type can precede a list of arguments. See:

- Struct Literals

- Struct Constructors

- Uniform construction syntax for built-in scalar types

Index Operations

IndexOperation:
[ ArgumentList ]

The base PostfixExpression is evaluated. The special variable $ is declared and set to be the number of elements in the base PostfixExpression (when available). A new declaration scope is created for the evaluation of the ArgumentList and $ appears in that scope only.

- If the PostfixExpression is an expression of static or dynamic array type, the result of the indexing is an lvalue of the ith element in the array, where i is an integer evaluated from ArgumentList. See array indexing.

- If PostfixExpression is a pointer p, the result is *(p + i) (see Pointer Arithmetic).

- If the base PostfixExpression is a ValueSeq then the ArgumentList must consist of only one argument, and that must be statically evaluatable to an integral constant. That integral constant n then selects the nth expression in the ValueSeq, which is the result of the IndexOperation. It is an error if n is out of bounds of the ValueSeq.

The index operator can be overloaded. Using multiple indices in ArgumentList is only supported for operator overloading.

Slice Operations

SliceOperation:
[ ]
[ Slice ]
[ Slice , ]

Slice:
AssignExpression
AssignExpression , Slice
AssignExpression .. AssignExpression
AssignExpression .. AssignExpression , Slice

The base PostfixExpression is evaluated. The special variable $ is declared and set to be the number of elements in the PostfixExpression (when available). A new declaration scope is created for the evaluation of the AssignExpression .. AssignExpression and $ appears in that scope only.

- If the base PostfixExpression is a static or dynamic array a, the result of the slice is a dynamic array referencing elements a[i] to a[j-1] inclusive, where i and j are integers evaluated from the first and second AssignExpression respectively. See array slicing.

- If the base PostfixExpression is a pointer p, the result will be a dynamic array referencing elements from p[i] to p[j-1] inclusive, where i and j are integers evaluated from the first and second AssignExpression respectively.

- If the base PostfixExpression is a ValueSeq, then the result of the slice is a new ValueSeq formed from the upper and lower bounds, which must statically evaluate to integral constants. It is an error if those bounds are out of range.

The first AssignExpression is taken to be the inclusive lower bound of the slice, and the second AssignExpression is the exclusive upper bound. The result of the expression is a slice of the elements in PostfixExpression.

If the [ ] form is used, the slice is of all the elements in the base PostfixExpression. The base expression cannot be a pointer.

The slice operator can be overloaded. Using more than one Slice is only supported for operator overloading.

A SliceOperation is not a modifiable lvalue.

Slice Conversion to Static Array

If the slice bounds can be known at compile time, the slice expression may be implicitly convertible to a static array lvalue. For example:

arr[a .. b]     // typed T[]

If both a and b are integers (which may be constant-folded), the slice expression can be converted to a static array of type T[b - a].

Note: a static array can also be assigned from a slice, performing a runtime check that the lengths match.

void f(int[2] sa) {}

int[] arr = [1, 2, 3];

void test()
{
//f(arr); // error, can't convert
f(arr[1 .. 3]); // OK
//f(arr[0 .. 3]); // error

int[2] g() { return arr[0 .. 2]; }
}

void bar(ref int[2] a)
{
assert(a == [2, 3]);
a = [4, 5];
}

void main()
{
int[] arr = [1, 2, 3];

// slicing an lvalue gives an lvalue
bar(arr[1 .. 3]);
assert(arr == [1, 4, 5]);
}

Primary Expressions

PrimaryExpression:
Identifier
. Identifier
TemplateInstance
. TemplateInstance
$
LiteralExpression
AssertExpression
MixinExpression
ImportExpression
NewExpression
FundamentalType . Identifier
TypeCtoropt ( Type ) . Identifier
( Type ) . TemplateInstance
FundamentalType ( NamedArgumentListopt )
TypeCtoropt ( Type ) ( NamedArgumentListopt )
Typeof
TypeidExpression
IsExpression
( Expression )
SpecialKeyword
RvalueExpression
TraitsExpression

LiteralExpression:
this
super
null
true
false
IntegerLiteral
FloatLiteral
CharacterLiteral
StringLiteral
InterpolationExpressionSequence
ArrayLiteral
AssocArrayLiteral
FunctionLiteral

Expression

Description

. Identifier

Module Scope Operator

$

Number of elements in an object being indexed/sliced.

( Type ). Identifier

Access a type property or a static member of a type.

FundamentalType (arg)

Uniform construction of scalar type with optional argument.

( Type )(args)

Construct a type with optional arguments.

( Expression )

Evaluate an expression - useful as a subexpression.

this

Within a constructor or non-static member function, this resolves to a reference to the object for which the function was called.

typeof(this) is valid anywhere inside an aggregate type definition. If a class member function is called with an explicit reference to typeof(this), a non-virtual call is made:

class A
{
char get() { return 'A'; }

char foo() { return typeof(this).get(); } // calls `A.get`
char bar() { return this.get(); } // dynamic, same as just `get()`
}

class B : A
{
override char get() { return 'B'; }
}

void main()
{
B b = new B();

assert(b.foo() == 'A');
assert(b.bar() == 'B');
}

Assignment to this is not allowed for classes.

See also:

- Delegating Constructors

- template this parameters

super

super is identical to this, except that it is cast to this's base class. It is an error if there is no base class. (The only extern(D) class without a base class is Object, however, note that extern(C++) classes have no base class unless specified.) If a member function is called with an explicit reference to super, a non-virtual call is made.

Assignment to super is not allowed.

See also: Base Class Construction.

null

null represents the null value for pointers, pointers to functions, delegates, dynamic arrays, associative arrays, and class objects. If it has not already been cast to a type, it is given the singular type typeof(null) and it is an exact conversion to convert it to the null value for pointers, pointers to functions, delegates, etc. After it is cast to a type, such conversions are implicit, but no longer exact.

String Literals

See StringLiteral grammar.

String literals are read-only. A string literal without a StringPostfix can implicitly convert to any of the following types, which have equal weight:

immutable(char)*

immutable(wchar)*

immutable(dchar)*

immutable(char)[]

immutable(wchar)[]

immutable(dchar)[]

Undefined Behavior: writing to a string literal. This is not allowed in @safe code.

By default, a string literal is typed as a dynamic array, but the element count is known at compile time. So all string literals can be implicitly converted to an immutable static array:

void foo(char[2] a)
{
assert(a[0] == 'b');
}
void bar(ref const char[2] a)
{
assert(a == "bc");
}

void main()
{
foo("bc");
foo("b"); // OK
//foo("bcd"); // error, too many chars
bar("bc"); // OK, same length
//bar("b"); // error, lengths must match
}

A string literal converts to a static array rvalue of the same or longer length. Any extra elements are padded with zeros. A string literal can also convert to a static array lvalue of the same length.

String literals have a '\0' appended to them, which makes them easy to pass to C or C++ functions expecting a null-terminated const char* string. The '\0' is not included in the .length property of the string literal.

Concatenation of string literals requires the use of the ~ operator, and is resolved at compile time. C style implicit concatenation without an intervening operator is error prone and not supported in D.

Hex String Literals

Because hex string literals contain binary data not limited to textual data, they allow additional conversions over other string literals.

A hex string literal implicitly converts to a constant byte[] or ubyte[].

immutable ubyte[] b = x"3F 80 00 00";
const byte[] c = x"3F 80 00 00";

A hex string literal can be explicitly cast to an array of integers with a larger size than 1. A big endian byte order in the hex string will be assumed.

static immutable uint[] data = cast(immutable uint[]) x"AABBCCDD";
static assert(data[0] == 0xAABBCCDD);

This requires the length of the hex string to be a multiple of the array element's size in bytes.

static e = cast(immutable ushort[]) x"AA BB CC";
// Error, length of 3 bytes is not a multiple of 2, the size of a `ushort`

When a hex string literal gets constant folded, the result is no longer considered a hex string literal

static immutable byte[] b = x"AA" ~ "G"; // Error: cannot convert `string` to `immutable byte[]`

Array Literals

ArrayLiteral:
[ ArgumentListopt ]

An array literal is a comma-separated list of expressions between square brackets [ and ]. The expressions form the elements of a dynamic array. The length of the array is the number of elements.

The element type of the array is inferred as the common type of all the elements, and each expression is implicitly converted to that type. When there is an expected array type, the elements of the literal will be implicitly converted to the expected element type.

auto a1 = [1, 2, 3];   // type is int[], with elements 1, 2 and 3
auto a2 = [1u, 2, 3];  // type is uint[], with elements 1u, 2u, and 3u
byte[] a3 = [1, 2, 3]; // OK
byte[] a4 = [128];     // error

By default, an array literal is typed as a dynamic array, but the element count is known at compile time. Therefore, an array literal can be implicitly converted to a static array of the same length.

int[2] sa = [1, 2]; // OK
int[2] sb = [1];    // error

Note: Slicing a dynamic array with a statically known slice length also allows conversion to a static array.

If any ArrayMemberInitialization is a ValueSeq, then the elements of the ValueSeq are inserted as expressions in place of the sequence.

GC Allocation

Escaping array literals are always allocated on the memory managed heap. Thus, they can be returned safely from functions:

int[] foo()
{
return [1, 2, 3];
}

An array literal is not GC allocated if:

- It initializes or assigns to a static array.

- It is an argument to a scope function parameter.

- It initializes a scope slice (-preview=dip1000 is required for this).

- It is used on one side of an EqualExpression or RelExpression.

- It is immediately indexed and used as an rvalue.

- It is used as a foreach aggregate where the element variable is not ref.

void f(scope int[] a, int[2] sa) @nogc
{
sa = [7, 8];
}
void g(int[] b) @nogc; // `b` is not scope, so may escape

void main() @nogc
{
int[3] sa = [1, 2, 3];
f([1, 2], [3, 4]);
//scope int[] a = [5, 6]; // requires `-preview=dip1000`
//g([1, 2]); // error, array literal heap allocated
assert([1, 2] < [3, 2]);
assert([1, 2][1] == 2);
foreach (e; [4, 2, 9])
assert(e > 0);
}

Casting

When array literals are cast to another array type, each element of the array is cast to the new element type. When arrays that are not literals are cast, the array is reinterpreted as the new type, and the length is recomputed:

// cast array literal
const ubyte[] ct = cast(ubyte[]) [257, 257];
// this is equivalent to:
// const ubyte[] ct = [cast(ubyte) 257, cast(ubyte) 257];
writeln(ct);  // writes [1, 1]

// cast other array expression
// --> normal behavior of CastExpression
byte[] arr = [1, 1];
short[] rt = cast(short[]) arr;
writeln(rt);  // writes [257]

In other words, casting an array literal will change the type of each initializer element.
Best Practices: Avoid casting an array literal when the elements could implicitly convert to an expected type. Instead, declare a variable of that type and initialize it with the array literal. Casting is more bug-prone than implicit conversions.

Associative Array Literals

AssocArrayLiteral:
[ KeyValuePairs ]

KeyValuePairs:
KeyValuePair
KeyValuePair , KeyValuePairs

KeyValuePair:
KeyExpression : ValueExpression

KeyExpression:
AssignExpression

ValueExpression:
AssignExpression

Associative array literals are a comma-separated list of key:value pairs between square brackets [ and ]. The list cannot be empty. The common type of the all keys is taken to be the key type of the associative array, and all keys are implicitly converted to that type. The common type of the all values is taken to be the value type of the associative array, and all values are implicitly converted to that type. An AssocArrayLiteral cannot be used to statically initialize anything.

[21u: "he", 38: "ho", 2: "hi"]; // type is string[uint],
// with keys 21u, 38u and 2u
// and values "he", "ho", and "hi"

If any of the keys or values in the KeyValuePairs are a ValueSeq, then the elements of the ValueSeq are inserted as arguments in place of the sequence.

Associative array initializers may contain duplicate keys, however, in that case, the last KeyValuePair lexicographically encountered is stored.

auto aa = [21: "he", 38: "ho", 2: "hi", 2:"bye"];
assert(aa[2] == "bye")

Function Literals

FunctionLiteral:
function RefOrAutoRefopt BasicTypeWithSuffixesopt ParameterWithAttributesopt FunctionLiteralBody
delegate RefOrAutoRefopt BasicTypeWithSuffixesopt ParameterWithMemberAttributesopt FunctionLiteralBody
RefOrAutoRefopt ParameterWithMemberAttributes FunctionLiteralBody
BlockStatement
Identifier => AssignExpression

BasicTypeWithSuffixes:
BasicType TypeSuffixesopt

ParameterWithAttributes:
Parameters FunctionAttributesopt

ParameterWithMemberAttributes:
Parameters MemberFunctionAttributesopt

FunctionLiteralBody:
=> AssignExpression
SpecifiedFunctionBody

RefOrAutoRef:
ref
auto ref

FunctionLiterals enable embedding anonymous functions and anonymous delegates directly into expressions. Short function literals are known as lambdas.

- Type is the return type of the function or delegate - if omitted it is inferred.

- ParameterWithAttributes or ParameterWithMemberAttributes can be used to specify the parameters for the function. If these are omitted, the function defaults to the empty parameter list ( ).

- Parameter types can be omitted.

- The type of a function literal is a delegate or a pointer to function.

For example:

int function(char c) fp; // declare pointer to a function

void test()
{
static int foo(char c) { return 6; }

fp = &foo;
}

is exactly equivalent to:

int function(char c) fp;

void test()
{
fp = function int(char c) { return 6; };
}

A delegate is necessary if the FunctionLiteralBody accesses any non-static local variables in enclosing functions.

int abc(int delegate(int i));

void test()
{
int b = 3;
int foo(int c) { return 6 + b; }

abc(&foo);
}

is exactly equivalent to:

int abc(int delegate(int i));

void test()
{
int b = 3;

abc( delegate int(int c) { return 6 + b; } );
}

The use of ref declares that the return value is returned by reference:

void main()
{
int x;
auto dg = delegate ref int() { return x; };
dg() = 3;
assert(x == 3);
}

Note: When comparing function literals with nested functions, the function form is analogous to static or non-nested functions, and the delegate form is analogous to non-static nested functions. I.e. a delegate literal can access non-static local variables in an enclosing function, a function literal cannot.

Delegate Inference

If a literal omits function or delegate and there's no expected type from the context, then it is inferred to be a delegate if it accesses a variable in an enclosing function, otherwise it is a function pointer.

void test()
{
int b = 3;

auto fp = (uint c) { return c * 2; }; // inferred as function pointer
auto dg = (int c) { return 6 + b; }; // inferred as delegate

static assert(!is(typeof(fp) == delegate));
static assert(is(typeof(dg) == delegate));
}

If a delegate is expected, the literal will be inferred as a delegate even if it accesses no variables from an enclosing function:

void abc(int delegate(int i)) {}
void def(uint function(uint s)) {}

void test()
{
int b = 3;

abc( (int c) { return 6 + b; } );  // inferred as delegate
abc( (int c) { return c * 2; } );  // inferred as delegate

def( (uint c) { return c * 2; } ); // inferred as function
//def( (uint c) { return c * b; } );  // error!
// Because the FunctionLiteral accesses b, its type
// is inferred as delegate. But def cannot accept a delegate argument.
}

Parameter Type Inference

If the type of a function literal can be uniquely determined from its context, parameter type inference is possible.

void foo(int function(int) fp);

void test()
{
int function(int) fp = (n) { return n * 2; };
// The type of parameter n is inferred as int.

foo((n) { return n * 2; });
// The type of parameter n is inferred as int.
}

auto fp = (i) { return 1; }; // error, cannot infer type of `i`

Function Literal Aliasing

Function literals can be aliased. Aliasing a function literal with unspecified parameter types produces a function template with type parameters for each unspecified parameter type of the literal. Type inference for the literal is then done when the template is instantiated.

alias fpt = (i) { return i; }; // ok, infer type of `i` when used
//auto fpt(T)(T i) { return i; } // equivalent

auto v = fpt(4);    // `i` is inferred as int
auto d = fpt(10.3); // `i` is inferred as double

alias fp = fpt!float;
auto f = fp(0); // f is a float

Return Type Inference

The return type of the FunctionLiteral can be inferred from either the AssignExpression, or any ReturnStatements in the BlockStatement. If there is a different expected type from the context, and the initial inferred return type implicitly converts to the expected type, then the return type is inferred as the expected type.

auto fi = (int i) { return i; };
static assert(is(typeof(fi(5)) == int));

long function(int) fl = (int i) { return i; };
static assert(is(typeof(fl(5)) == long));

Nullary Short Syntax

Parameters can be omitted completely for a function literal when there is a BlockStatement function body.

Note: This form is not allowed to be immediately called as an ExpressionStatement, because it would require arbitrary lookahead to distinguish it from a BlockStatement.

auto f = { writeln("hi"); }; // OK, f has type `void function()`
f();
{ writeln("hi"); }(); // error
() { writeln("hi"); }(); // OK

Anonymous delegates can behave like arbitrary statement literals. For example, here an arbitrary statement is executed by a loop:

void loop(int n, void delegate() statement)
{
foreach (_; 0 .. n)
{
statement();
}
}

void main()
{
int n = 0;

loop(5, { n += 1; });
assert(n == 5);
}

Shortened Body Syntax

The syntax => AssignExpression is equivalent to { return AssignExpression; }.

void main()
{
auto i = 3;
auto twice = function (int x) => x * 2;
assert(twice(i) == 6);

auto square = delegate () => i * i;
assert(square() == 9);

auto n = 5;
auto mul_n = (int x) => x * n;
assert(mul_n(i) == 15);
}

The syntax Identifier => AssignExpression is equivalent to (Identifier) { return AssignExpression; }.

// the following two declarations are equivalent
alias fp = i => 1;
alias fp = (i) { return 1; };

Best Practices: The minimal form of the function literal is most useful as an argument to a template alias parameter:

int motor(alias fp)(int i)
{
return fp(i) + 1;
}

int engine()
{
return motor!(i => i * 2)(6); // returns 13
}

Note: The syntax Identifier { statement; } is not supported because it is easily confused with statements x = Identifier; { statement; }; if the semicolons were accidentally omitted.

Uniform construction syntax for built-in scalar types

The implicit conversions of built-in scalar types can be explicitly represented by using function call syntax. For example:

auto a = short(1);  // implicitly convert an integer literal '1' to short
auto b = double(a); // implicitly convert a short variable 'a' to double
auto c = byte(128); // error, 128 cannot be represented in a byte

If the argument is omitted, it means default construction of the scalar type:

auto a = ushort();  // same as: ushort.init
auto b = wchar();   // same as: wchar.init

The argument may not be given a name:

auto a = short(x: 1); // Error

See also: Usual Arithmetic Conversions.

Assert Expressions

AssertExpression:
assert ( AssertArguments )

AssertArguments:
AssignExpression
AssignExpression ,
AssignExpression , AssignExpression
AssignExpression , AssignExpression ,

The first AssignExpression is evaluated and converted to a boolean value. If the value is not true, an Assert Failure has occurred and the program enters an Invalid State.

int i = fun();
assert(i > 0);

AssertExpression has different semantics if it is in a unittest or in contract.

If the first AssignExpression is a reference to a class instance for which a class Invariant exists, the class Invariant must hold.

If the first AssignExpression is a pointer to a struct instance for which a struct Invariant exists, the struct Invariant must hold.

The type of an AssertExpression is void.

Undefined Behavior: Once in an Invalid State the behavior of the continuing execution of the program is undefined.

Implementation Defined: Whether the first AssertExpression is evaluated or not (at runtime) is typically set with a compiler switch. If it is not evaluated, any side effects specified by the AssertExpression may not occur. The behavior when the first AssertExpression evaluates to false is also typically set with a compiler switch, and may include these options:

1. Immediately halting via execution of a special CPU instruction

2. Aborting the program

3. Calling the assert failure function in the corresponding C runtime library

4. Throwing the AssertError exception in the D runtime library

Note: Throwing AssertError is the default for dmd, with an optional -checkaction=context switch to show certain sub-expressions used in the first AssertExpression in the error message:

auto x = 4;
assert(x < 3);

When in use, the above will throw an AssertError with a message 4 >= 3.

Best Practices:

1. Do not have side effects in either AssignExpression that subsequent code depends on.

2. AssertExpressions are intended to detect bugs in the program. Do not use them for detecting input or environmental errors.

3. Do not attempt to resume normal execution after an Assert Failure.

Compile-time Evaluation

If the first AssignExpression consists entirely of compile time constants, and evaluates to false, it is a special case - it signifies that subsequent statements are unreachable code. Compile Time Function Execution (CTFE) calls are not attempted for the evaluation. Such an AssertExpression has type noreturn.

This allows the compiler to suppress an error when there is a missing return statement:

int f(int x)
{
if (x > 0)
{
return 5 / x;
}
assert(0);
// no need to use a dummy return statement here
}

The implementation may handle the case of the first AssignExpression evaluating to false at compile time differently - even when other asserts are ignored, it may still generate a HLT instruction or equivalent.

Rationale: Halting the program prevents undefined behaviour from occurring.

See also: static assert.

Assert Message

The second AssignExpression, if present, must be implicitly convertible to type const(char)[]. When present, the implementation may evaluate it and print the resulting message upon assert failure:

void main()
{
assert(0, "an" ~ " error message");
}

When compiled and run, typically it will produce the message:

core.exception.AssertError@test.d(3) an error message

Mixin Expressions

MixinExpression:
mixin ( ArgumentList )

Each AssignExpression in the ArgumentList is evaluated at compile time, and the result must be representable as a string. The resulting strings are concatenated to form a string. The text contents of the string must be compilable as a valid Expression, and is compiled as such.

int foo(int x)
{
return mixin("x +", 1) * 7;  // same as ((x + 1) * 7)
}

Import Expressions

ImportExpression:
import ( AssignExpression )

The AssignExpression must evaluate at compile time to a constant string. The text contents of the string are interpreted as a file name. The file is read, and the exact contents of the file become a hex string literal.

Implementations may restrict the file name in order to avoid directory traversal security vulnerabilities. A possible restriction might be to disallow any path components in the file name.

Note that by default an import expression will not compile unless one or more paths are passed via the -J switch. This tells the compiler where it should look for the files to import. This is a security feature.

void foo()
{
// Prints contents of file foo.txt
writeln(import("foo.txt"));
}

New Expressions

NewExpression:
new Type
new Type [ AssignExpression ]
new Type ( NamedArgumentListopt )
NewAnonClassExpression

NewExpressions allocate memory on the garbage collected heap by default.

new T constructs an instance of type T and default-initializes it. The result's type is:

- T when T is a reference type (e.g. classes, associative arrays)

- T* when T is a value type (e.g. basic types, structs)

int* i = new int;
assert(*i == 0); // int.init

Object o = new Object;
//int[] a = new int[]; // error, need length argument

The Type(NamedArgumentList) form allows passing either a single initializer of the same type, or multiple arguments for more complex types:

- For class and struct types, NamedArgumentList is passed to the constructor.

- For a dynamic array, the argument sets the initial array length.

- For multidimensional dynamic arrays, each argument corresponds to an initial length (see below).

int* i = new int(5);
assert(*i == 5);

Exception e = new Exception("info");
assert(e.msg == "info");

int[] a = new int[](2);
assert(a.length == 2);
a = new int[2]; // same, see below

The Type[AssignExpression] form allocates a dynamic array with length equal to AssignExpression. It is preferred to use the Type(NamedArgumentList) form when allocating dynamic arrays instead, as it is more general.

Note: It is not possible to allocate a static array directly with new (only by using a type alias).

The result is a unique expression which can implicitly convert to other qualifiers:

immutable o = new Object;

Class Instantiation

If a NewExpression is used with a class type as an initializer for a function local variable with scope storage class, then the instance is allocated on the stack.

new can also be used to allocate a nested class.

Multidimensional Arrays

To allocate multidimensional arrays, the declaration reads in the same order as the prefix array declaration order.

char[][] foo;   // dynamic array of strings
...
foo = new char[][30]; // allocate array of 30 strings

The above allocation can also be written as:

foo = new char[][](30); // allocate array of 30 strings

To allocate the nested arrays, multiple arguments can be used:

int[][][] bar;
bar = new int[][][](5, 20, 30);

assert(bar.length == 5);
assert(bar[0].length == 20);
assert(bar[0][0].length == 30);

The assignment above is equivalent to:

bar = new int[][][5];
foreach (ref a; bar)
{
a = new int[][20];
foreach (ref b; a)
{
b = new int[30];
}
}

Typeid Expressions

TypeidExpression:
typeid ( Type )
typeid ( Expression )

If Type, returns an instance of class TypeInfo corresponding to Type.

If Expression, returns an instance of class TypeInfo corresponding to the type of the Expression. If the type is a class, it returns the TypeInfo of the dynamic type (i.e. the most derived type). The Expression is always executed.

class A { }
class B : A { }

void main()
{
import std.stdio;

writeln(typeid(int));        // int
uint i;
writeln(typeid(i++));        // uint
writeln(i);                  // 1
A a = new B();
writeln(typeid(a));          // B
writeln(typeid(typeof(a)));  // A
}

Is Expressions

IsExpression:
is ( Type )
is ( Type : TypeSpecialization )
is ( Type == TypeSpecialization )
is ( Type : TypeSpecialization , TemplateParameterList )
is ( Type == TypeSpecialization , TemplateParameterList )
is ( Type Identifier )
is ( Type Identifier : TypeSpecialization )
is ( Type Identifier == TypeSpecialization )
is ( Type Identifier : TypeSpecialization , TemplateParameterList )
is ( Type Identifier == TypeSpecialization , TemplateParameterList )

TypeSpecialization:
Type
TypeCtor
struct
union
class
interface
enum
__vector
function
delegate
super
return
__parameters
module
package

An IsExpression is evaluated at compile time and is used to check if an expression is a valid type. In addition, there are forms which can also:

- compare types for equivalence

- determine if one type can be implicitly converted to another

- deduce the subtypes of a type using pattern matching

- deduce the template arguments of a type template instance

The result of an IsExpression is a boolean which is true if the condition is satisfied and false if not.

Type is the type being tested. It must be syntactically correct, but it need not be semantically correct. If it is not semantically correct, the condition is not satisfied.

TypeSpecialization is the type that Type is being pattern matched against.

IsExpressions may be used in conjunction with typeof to check whether an expression type checks correctly. For example, is(typeof(foo)) will return true if foo has a valid type.

Basic Forms

is ( Type )

The condition is satisfied if Type is semantically correct. Type must be syntactically correct regardless.

pragma(msg, is(5)); // error
pragma(msg, is([][])); // error

int i;
static assert(is(int));
static assert(is(typeof(i))); // same

static assert(!is(Undefined));
static assert(!is(typeof(int))); // int is not an expression
static assert(!is(i)); // i is a value

alias Func = int(int); // function type
static assert(is(Func));
static assert(!is(Func[])); // fails as an array of functions is not allowed

is ( Type : TypeSpecialization )

The condition is satisfied if Type is semantically correct and it is the same as or can be implicitly converted to TypeSpecialization. TypeSpecialization is only allowed to be a Type.

alias Bar = short;
static assert(is(Bar : int)); // short implicitly converts to int
static assert(!is(Bar : string));

is ( Type == TypeSpecialization )

If TypeSpecialization is a type, the condition is satisfied if Type is semantically correct and is the same type as TypeSpecialization.

alias Bar = short;
static assert(is(Bar == short));
static assert(!is(Bar == int));

If TypeSpecialization is a TypeCtor then the condition is satisfied if Type is of that TypeCtor:

static assert(is(const int == const));
static assert(is(const int[] == const));
static assert(!is(const(int)[] == const)); // head is mutable
static assert(!is(immutable int == const));

If TypeSpecialization is one of struct union class interface enum __vector function delegate module package then the condition is satisfied if Type is one of those.

Object o;
static assert(!is(o == class)); // `o` is not a type
static assert(is(Object == class));
static assert(is(ModuleInfo == struct));
static assert(!is(int == class));

void f();
static assert(!is(f == function)); // `f` is not a type
static assert(is(typeof(f) == function));
static assert(!is(typeof(&f) == function)); // function pointer is not a function

The module and package forms are satisfied when Type is a symbol, not a type, unlike the other forms. The isModule and isPackage __traits should be used instead. Package modules are considered to be both packages and modules.

TypeSpecialization can also be one of these keywords:

keyword

condition

super

true if Type is a class or interface

return

true if Type is a function, delegate or function pointer

__parameters

true if Type is a function, delegate or function pointer

class C {}
static assert(is(C == super));

void foo(int i);
static assert(!is(foo == return));
static assert(is(typeof(foo) == return));
static assert(is(typeof(foo) == __parameters));

See also: Traits.

Identifier Forms

Identifier is declared to be an alias of the resulting type if the condition is satisfied. The Identifier forms can only be used if the IsExpression appears in a StaticIfCondition or the first argument of a StaticAssert.

is ( Type Identifier )

The condition is satisfied if Type is semantically correct. If so, Identifier is declared to be an alias of Type.

struct S
{
int i, j;
}
static assert(is(typeof(S.i) T) && T.sizeof == 4);

alias Bar = short;

void foo()
{
static if (is(Bar T))
alias S = T;
else
alias S = long;

pragma(msg, S); // short

// if T was defined, it remains in scope
if (is(T))
pragma(msg, T); // short

//if (is(Bar U)) {} // error, cannot declare U here
}

is ( Type Identifier : TypeSpecialization )

If TypeSpecialization is a type, the condition is satisfied if Type is semantically correct and it is the same as or can be implicitly converted to TypeSpecialization. Identifier is declared to be an alias of the TypeSpecialization.

alias Bar = int;

static if (is(Bar T : int))
alias S = T;
else
alias S = long;

static assert(is(S == int));

If TypeSpecialization is a type pattern involving Identifier, type deduction of Identifier is attempted based on either Type or a type that it implicitly converts to. The condition is only satisfied if the type pattern is matched.

struct S
{
long* i;
alias i this; // S converts to long*
}

static if (is(S U : U*)) // S is matched against the pattern U*
{
U u;
}
static assert(is(U == long));

The way the type of Identifier is determined is analogous to the way template parameter types are determined by TemplateTypeParameterSpecialization.

is ( Type Identifier == TypeSpecialization )

If TypeSpecialization is a type, the condition is satisfied if Type is semantically correct and is the same type as TypeSpecialization. Identifier is declared to be an alias of the TypeSpecialization.

const x = 5;

static if (is(typeof(x) T == const int))   // satisfied, T is now defined
alias S = T;

static assert(is(T)); // T is in scope
pragma(msg, T); // const int

If TypeSpecialization is a type pattern involving Identifier, type deduction of Identifier is attempted based on Type. The condition is only satisfied if the type pattern is matched.

alias Foo = long*;

static if (is(Foo U == U*)) // Foo is matched against the pattern U*
{
U u;
}
static assert(is(U == long));

If TypeSpecialization is a valid keyword for the is(Type == Keyword) form, the condition is satisfied in the same manner. Identifier is set as follows:

keyword

alias type for Identifier

struct

Type

union

Type

class

Type

interface

Type

super

TypeSeq of base classes and interfaces

enum

the base type of the enum

__vector

the static array type of the vector

function

TypeSeq of the function parameter types. For C- and D-style variadic functions, only the non-variadic parameters are included. For typesafe variadic functions, the ... is ignored.

delegate

the function type of the delegate

return

the return type of the function, delegate, or function pointer

__parameters

the parameter sequence of a function, delegate, or function pointer. This includes the parameter types, names, and default values.

const

Type

immutable

Type

inout

Type

shared

Type

module

the module

package

the package

enum E : byte { Emember }

static if (is(E V == enum))    // satisfied, E is an enum
V v;                       // v is declared to be a byte

static assert(is(V == byte));

Parameter List Forms

is ( Type : TypeSpecialization , TemplateParameterList )
is ( Type == TypeSpecialization , TemplateParameterList )
is ( Type Identifier : TypeSpecialization , TemplateParameterList )
is ( Type Identifier == TypeSpecialization , TemplateParameterList )

More complex types can be pattern matched. The TemplateParameterList declares symbols based on the parts of the pattern that are matched, analogously to the way implied template parameters are matched.

Example: Matching a Template Instantiation

struct Tuple(T...)
{
// ...
}
alias Tup2 = Tuple!(int, string);

static if (is(Tup2 : Template!Args, alias Template, Args...))
{
static assert(__traits(isSame, Template, Tuple));
static assert(is(Template!(int, string) == Tup2)); // same struct
}
static assert(is(Args[0] == int));
static assert(is(Args[1] == string));

Type cannot be matched when TypeSpecialization is an alias template instance:

struct S(T) {}
alias A(T) = S!T;

static assert(is(A!int : S!T, T));
//static assert(!is(A!int : A!T, T));

Example: Matching an Associative Array

alias AA = long[string];

static if (is(AA T : T[U], U : string)) // T[U] is the pattern
{
pragma(msg, T);  // long
pragma(msg, U);  // string
}

// no match, B is not an int
static assert(!is(AA A : A[B], B : int));

Example: Matching a Static Array

static if (is(int[10] W : W[len], int len)) // W[len] is the pattern
{
static assert(len == 10);
}
static assert(is(W == int));

// no match, len should be 10
static assert(!is(int[10] X : X[len], int len : 5));

Rvalue Expression

RvalueExpression:
__rvalue ( AssignExpression )

The RvalueExpression causes the embedded AssignExpression to be treated as an rvalue whether it is an rvalue or an lvalue.

Overloading

If both ref and non-ref parameter overloads are present, an rvalue is preferably matched to the non-ref parameters, and an lvalue is preferably matched to the ref parameter. An RvalueExpression will preferably match with the non-ref parameter.

Semantics of Arguments Matched to Rvalue Parameters

An rvalue argument is owned by the function called. Hence, if an lvalue is matched to the rvalue argument, a copy is made of the lvalue to be passed to the function. The function will then call the destructor (if any) on the parameter at the conclusion of the function. An rvalue argument is not copied, as it is assumed to already be unique, and is also destroyed at the conclusion of the function.

The called function's semantics are the same whether a parameter originated as an rvalue or is a copy of an lvalue. This means that an RvalueExpression argument destroys the expression upon function return. Attempts to continue to use the lvalue expression are invalid. The compiler won't always be able to detect a use after being passed to the function, which means that the destructor for the object must reset the object's contents to its initial value, or at least a benign value that can be destructed more than once.

struct S
{
ubyte* p;
~this()
{
free(p);
// add `p = null;` here to prevent double free
}
}

void aggh(S s)
{
}

void oops()
{
S s;
s.p = cast(ubyte*)malloc(10);
aggh(__rvalue(s));
// destructor of s called at end of scope, double-freeing s.p
}

RvalueExpressions enable the use of move constructors and move assignments.

As a function attribute

It is also allowed to use the __rvalue keyword as a function attribute. The presence of this attribute will infer the RvalueExpression upon the function call expression at the call-site, as if applied to the function's return value. This is only accepted on functions that return by reference.

ref S fun(return ref S s) __rvalue
{
return s;
}

S s;
S t = fun(s); // call inferred as: __rvalue(fun(s))

Special Keywords

SpecialKeyword:
__FILE__
__FILE_FULL_PATH__
__MODULE__
__LINE__
__FUNCTION__
__PRETTY_FUNCTION__

__FILE__ and __LINE__ expand to the source file name and line number at the point of instantiation. The path of the source file is left up to the compiler.

__FILE_FULL_PATH__ expands to the absolute source file name at the point of instantiation.

__MODULE__ expands to the module name at the point of instantiation.

__FUNCTION__ expands to the fully qualified name of the function at the point of instantiation.

__PRETTY_FUNCTION__ is similar to __FUNCTION__, but also expands the function return type, its parameter types, and its attributes.

Example:

module test;
import std.stdio;

void test(string file = __FILE__, size_t line = __LINE__,
string mod = __MODULE__, string func = __FUNCTION__,
string pretty = __PRETTY_FUNCTION__,
string fileFullPath = __FILE_FULL_PATH__)
{
writefln("file: '%s', line: '%s', module: '%s',\nfunction: '%s', " ~
"pretty function: '%s',\nfile full path: '%s'",
file, line, mod, func, pretty, fileFullPath);
}

int main(string[] args)
{
test();
return 0;
}

Assuming the file was at /example/test.d, this will output:

file: 'test.d', line: '13', module: 'test',
function: 'test.main', pretty function: 'int test.main(string[] args)',
file full path: '/example/test.d'

Warning: Do not use mixin(__FUNCTION__) to get the symbol for the current function. This seems to be a common thing for programmers to attempt when trying to get the symbol for the current function in order to do introspection on it, since D does not currently have a direct way to get that symbol. However, using mixin(__FUNCTION__) means that the symbol for the function will be looked up by name, which means that it's subject to the various rules that go with symbol lookup, which can cause various problems. One such problem would that if a function is overloaded, the result will be the first overload whether that's the current function or not.

Given that D doesn't currently have a way to directly get the symbol for the current function, the best way to do it is to get the parent symbol of a symbol within the function, since that avoids any issues surrounding symbol lookup rules. An example of that which doesn't rely on any other symbols is __traits(parent {}). It declares an anonymous, nested function, whose parent is then the current function. So, getting its parent gets the symbol for the current function.

Associativity and Commutativity

An implementation may rearrange the evaluation of expressions according to arithmetic associativity and commutativity rules as long as, within that thread of execution, no observable difference is possible.

This rule precludes any associative or commutative reordering of floating point expressions.

Statements

The order of execution within a function is controlled by Statements. A function's body consists of a sequence of zero or more Statements. Execution occurs in lexical order, though certain statements may have deferred effects. A Statement has no value; it is executed for its effects.

Statement:
EmptyStatement
NonEmptyStatement
ScopeBlockStatement

EmptyStatement:
;

NoScopeNonEmptyStatement:
NonEmptyStatement
BlockStatement

NoScopeStatement:
EmptyStatement
NonEmptyStatement
BlockStatement

NonEmptyOrScopeBlockStatement:
NonEmptyStatement
ScopeBlockStatement

NonEmptyStatement:
NonEmptyStatementNoCaseNoDefault
CaseStatement
CaseRangeStatement
DefaultStatement

NonEmptyStatementNoCaseNoDefault:
LabeledStatement
ExpressionStatement
DeclarationStatement
IfStatement
WhileStatement
DoStatement
ForStatement
ForeachStatement
SwitchStatement
FinalSwitchStatement
ContinueStatement
BreakStatement
ReturnStatement
GotoStatement
WithStatement
SynchronizedStatement
TryStatement
ScopeGuardStatement
AsmStatement
MixinStatement
ForeachRangeStatement
PragmaStatement
ConditionalStatement
StaticForeachStatement
ImportDeclaration

Any ambiguities in the grammar between Statements and Declarations are resolved by the declarations taking precedence. Wrapping such a statement in parentheses will disambiguate it in favor of being a Statement.

Scope Statements

ScopeStatement:
NonEmptyStatement
BlockStatement

A new scope for local symbols is introduced for the NonEmptyStatement or BlockStatement.

Even though a new scope is introduced, local symbol declarations cannot shadow (hide) other local symbol declarations in the same function.

void func1(int x)
{
int x;    // illegal, x shadows parameter x

int y;

{ int y; } // illegal, y shadows enclosing scope's y

void delegate() dg;
dg = { int y; }; // ok, this y is not in the same function

struct S
{
int y;    // ok, this y is a member, not a local
}

{ int z; }
{ int z; }  // ok, this z is not shadowing the other z

{ int t; }
{ t++;   }  // illegal, t is undefined
}

Best Practices: Local declarations within a function should all have unique names, even if they are in non-overlapping scopes.

Scope Block Statements

ScopeBlockStatement:
BlockStatement

A scope block statement introduces a new scope for the BlockStatement.

Labeled Statements

Statements can be labeled. A label is an identifier that precedes a statement.

LabeledStatement:
Identifier :
Identifier : Statement

Any statement can be labeled, including empty statements, and so can serve as the target of a goto statement. Labeled statements can also serve as the target of a break or continue statement.

A label can appear without a following statement at the end of a block.

Labels are in a name space independent of declarations, variables, types, etc. Even so, labels cannot have the same name as local declarations. The label name space is the body of the function they appear in. Label name spaces do not nest, i.e. a label inside a block statement is accessible from outside that block.

Labels in one function cannot be referenced from another function.

Block Statement

BlockStatement:
{ }
{ StatementList }

StatementList:
Statement
Statement StatementList

A block statement is a sequence of statements enclosed by { }. The statements are executed in lexical order, until the end of the block is reached or a statement transfers control elsewhere.

Expression Statement

ExpressionStatement:
Expression ;

The expression is evaluated.

Expressions that have no effect, like (x + x), are illegal as expression statements unless they are cast to void.

int x;
x++;               // ok
x;                 // illegal
1+1;               // illegal
cast(void)(x + x); // ok

Declaration Statement

Declaration statements define variables, and declare types, templates, functions, imports, conditionals, static foreaches, and static asserts.

DeclarationStatement:
StorageClassesopt Declaration

Some declaration statements:

int a;        // declare a as type int and initialize it to 0
struct S { }  // declare struct s
alias myint = int;

If Statement

If statements provide simple conditional execution of statements.

IfStatement:
if ( IfCondition ) ThenStatement
if ( IfCondition ) ThenStatement else ElseStatement

IfCondition:
Expression
IfConditionStorageClasses Identifier = Expression
IfConditionStorageClassesopt BasicType Declarator = Expression

IfConditionStorageClasses:
IfConditionStorageClass
IfConditionStorageClass IfConditionStorageClasses

IfConditionStorageClass:
scope
auto
ref
TypeCtor

ThenStatement:
ScopeStatement

ElseStatement:
ScopeStatement

If there is a declared Identifier variable, it is evaluated. Otherwise, Expression is evaluated. The result is converted to a boolean.

If the boolean is true, the ThenStatement is transferred to, otherwise the ElseStatement is transferred to.

The ElseStatement is associated with the innermost if statement which does not already have an associated ElseStatement.

Boolean Conversion

The following type instances are supported in a condition:

- Fundamental types are true when non-zero

- Pointers are true when non-null

- Reference types are true when non-null

- A user-defined type with a valid opCast!bool() method will be called for the result. Otherwise, any alias this member declaration will be used if its target type itself matches one of these rules.

- An enum type is true if its base type matches one of these rules.

If none of these are valid, it is an error to use the value in a condition.

Note: A dynamic array a can be used in a condition, which is true when a !is []. However, using an array as a boolean condition should be avoided as it can be bug-prone. This may be disallowed in a future edition.

Condition Variables

When an Identifier form of IfCondition is used, a variable is declared with that name and initialized to the value of the Expression.

- If both auto and ref are present among IfConditionStorageClasses, the variable is a reference if and only if the Expression is an lvalue;

- If ref is present without auto, the variable is a reference and Expression must be an lvalue.

- If auto is present without ref, the type of the variable is the same as Expression. The presence of auto in combination with any IfConditionStorageClasses except ref (cf. first bullet point) has no effect. Only if no other IfConditionStorageClasses are meaningful, auto provides an option to declare a variable. Using auto without ref when BasicType is given is not allowed.

- If TypeCtors are provided and possibly the BasicType is given, the variable is declared as it would for a normal variable declaration.

The scope of the variable is the ThenStatement only.

import std.regex;

if (auto m = matchFirst("abcdef", "b(c)d"))
{
writefln("[%s]", m.pre);    // prints [a]
writefln("[%s]", m.post);   // prints [ef]
writefln("[%s]", m[0]);     // prints [bcd]
writefln("[%s]", m[1]);     // prints [c]
}
else
{
writeln("no match");
//writeln(m.post); // Error: undefined identifier 'm'
}
//writeln(m.pre);      // Error: undefined identifier 'm'

While Statement

WhileStatement:
while ( IfCondition ) ScopeStatement

A While Statement implements a simple loop.

If the IfCondition is an Expression, it is evaluated and must have a type that can be converted to a boolean. If it's true the ScopeStatement is executed. After the ScopeStatement is executed, the Expression is evaluated again, and if true the ScopeStatement is executed again. This continues until the Expression evaluates to false.

int i = 0;
while (i < 10)
{
foo(i);
++i;
}

If the IfCondition declares a variable, it is initialized and evaluated on every loop iteration.

A BreakStatement will exit the loop.

A ContinueStatement will transfer directly to evaluating IfCondition again.

Do Statement

DoStatement:
do ScopeStatement  while ( Expression ) ;

Do while statements implement simple loops.

ScopeStatement is executed. Then Expression is evaluated and must have a type that can be converted to a boolean. If it's true the loop is iterated again. This continues until the Expression evaluates to false.

int i = 0;
do
{
foo(i);
} while (++i < 10);

A BreakStatement will exit the loop. A ContinueStatement will transfer directly to evaluating Expression again.

For Statement

For statements implement loops with initialization, test, and increment clauses.

ForStatement:
for ( Initialize Testopt ; Incrementopt ) ScopeStatement

Initialize:
;
NoScopeNonEmptyStatement

Test:
Expression

Increment:
Expression

Initialize is executed. Test is evaluated and must have a type that can be converted to a boolean. If Test is true the ScopeStatement is executed. After execution, Increment is executed. Then Test is evaluated again, and if true the ScopeStatement is executed again. This continues until the Test evaluates to false.

A BreakStatement will exit the loop. A ContinueStatement will transfer directly to the Increment.

A ForStatement creates a new scope. If Initialize declares a variable, that variable's scope extends through ScopeStatement. For example:

for (int i = 0; i < 10; i++)
foo(i);

is equivalent to:

{
int i;
for (i = 0; i < 10; i++)
foo(i);
}

ScopeStatement cannot be an empty statement:

for (int i = 0; i < 10; i++)
;       // illegal

Use instead:

for (int i = 0; i < 10; i++)
{
}

Initialize may be just ;. Test may be omitted, and if so, it is treated as if it evaluated to true.

Best Practices: Consider replacing ForStatements with Foreach Statements or Foreach Range Statements. Foreach loops are easier to understand, less prone to error, and easier to refactor.

Foreach Statement

A foreach statement iterates a series of values.

AggregateForeach:
Foreach ( ForeachTypeList ; ForeachAggregate )

ForeachStatement:
AggregateForeach NoScopeNonEmptyStatement

Foreach:
foreach
foreach_reverse

ForeachTypeList:
ForeachType
ForeachType , ForeachTypeList

ForeachType:
ForeachTypeAttributesopt BasicType Declarator
ForeachTypeAttributesopt Identifier
ForeachTypeAttributesopt alias Identifier

ForeachTypeAttributes:
ForeachTypeAttribute
ForeachTypeAttribute ForeachTypeAttributes

ForeachTypeAttribute:
enum
ref
scope
TypeCtor

ForeachAggregate:
Expression

ForeachAggregate is evaluated. It must evaluate to an expression which is a static array, dynamic array, associative array, struct, class, delegate, or sequence. The NoScopeNonEmptyStatement is executed, once for each element of the aggregate.

The number of variables declared in ForeachTypeList depends on the kind of aggregate. The declared variables are set at the start of each iteration.

- By default a single declared variable is a copy of the current element.

- If the ForeachTypeAttribute is ref, that variable will be a reference to the current element of the aggregate.

- If the ForeachTypeAttribute is scope, the variable will have scope semantics.

If not specified, the type of a ForeachType variable can be inferred from the type of the ForeachAggregate. Note that auto is not a valid ForeachTypeAttribute. The two foreach statements below are equivalent:

int[] arr = [1, 2, 3];

foreach (int n; arr)
writeln(n);

foreach (n; arr) // ok, n is an int
writeln(n);

The aggregate must be loop invariant, meaning that elements cannot be added or removed from it in the NoScopeNonEmptyStatement.

A BreakStatement in the body of the foreach will exit the loop. A ContinueStatement will immediately start the next iteration.

Foreach over Arrays

If the aggregate is a static or dynamic array, there can be one or two variables declared. If one, then the variable is said to be the value, which is set successively to each element of the array. The type of the variable, if specified, must be compatible with the array element type (except for the special handling of character elements outlined below). The value variable can modify array elements when declared with ref.

If there are two variables declared, the first is said to be the index and the second is said to be the value as above. index cannot be declared with ref. It is set to the index of the array element on each iteration. The index type can be inferred:

char[] a = ['h', 'i'];

foreach (i, char c; a)
{
writefln("a[%d] = '%c'", i, c);
}

For a dynamic array, the index type must be compatible with size_t, unless the array length is statically known to fit in a smaller integer type. Static arrays may use any integral type that spans the length of the array.

For foreach, the elements for the array are iterated over starting at index 0 and continuing to the last element of the array. For foreach_reverse, the array elements are visited in the reverse order.

Foreach over Arrays of Characters

If the aggregate expression is a static or dynamic array of chars, wchars, or dchars, then the type of the value variable can be any of char, wchar, or dchar. In this manner any UTF array can be decoded into any UTF type:

char[] a = "\xE2\x89\xA0".dup;  // \u2260 encoded as 3 UTF-8 bytes

foreach (dchar c; a)
{
writefln("a[] = %x", c); // prints 'a[] = 2260'
}

dchar[] b = "\u2260"d.dup;

foreach (char c; b)
{
writef("%x, ", c);  // prints 'e2, 89, a0, '
}

Aggregates can be string literals, which can be accessed as char, wchar, or dchar arrays:

foreach (char c; "ab")
{
writefln("'%s'", c);
}
foreach (wchar w; "xy")
{
writefln("'%s'", w);
}

which would print:

'a'
'b'
'x'
'y'

Foreach over Associative Arrays

If the aggregate expression is an associative array, there can be one or two variables declared. If one, then the variable is said to be the value, which is set to each value in the array, one by one. If the type of the variable is provided, it must implicitly convert from the associative array value type.

// value type is int
int[string] userAges = ["john":30, "sue":32];

foreach (ref age; userAges)
{
age++;
}
assert(userAges == ["john":31, "sue":33]);

If there are two variables declared, the first is said to be the index and the second is said to be the value. The index variable type (if given) must be implicitly convertible from the key type of the associative array. The index variable is set to the key corresponding to each value in the associative array.

// index type is string, value type is double
double[string] aa = ["pi":3.14, "e":2.72];

foreach (string s, double d; aa)
{
writefln("aa['%s'] = %g", s, d);
}

Implementation Defined: The order in which the elements of the array are iterated over is unspecified for foreach. This is why foreach_reverse for associative arrays is illegal.

If an index or value variable is declared as ref, then any type given must match the associative array key or value type respectively, or const-convert from it. A ref index variable must be (inferred as) const, as keys must not be mutated.

Foreach over Structs and Classes with opApply

If the aggregate expression is a struct or class object, the foreach is defined by the special opApply member function, and the foreach_reverse behavior is defined by the special opApplyReverse member function. These functions must each have the signature below:

OpApplyDeclaration:
int opApply ( scope int delegate ( OpApplyParameters ) dg ) ;

OpApplyParameters:
OpApplyParameter
OpApplyParameter, OpApplyParameters

OpApplyParameter:
ForeachTypeAttributesopt BasicType Declarator

where each OpApplyParameter of dg must match a ForeachType in a ForeachStatement, otherwise the ForeachStatement will cause an error.

Any ForeachTypeAttribute cannot be enum.

To support a ref iteration variable, the delegate must take a ref parameter:

struct S
{
int opApply(scope int delegate(ref uint n) dg);
}
void f(S s)
{
foreach (ref uint i; s)
i++;
}

Above, opApply is still matched when i is not ref, so by using a ref delegate parameter both forms are supported.

There can be multiple opApply and opApplyReverse functions - one is selected by matching each parameter of dg to each ForeachType declared in the ForeachStatement.

The body of the apply function iterates over the elements it aggregates, passing each one in successive calls to the dg delegate. The delegate return value determines whether to interrupt iteration:

- If the result is nonzero, apply must cease iterating and return that value.

- If the result is 0, then iteration should continue. If there are no more elements to iterate, apply must return 0.

The result of calling the delegate will be nonzero if the ForeachStatement body executes a matching BreakStatement, ReturnStatement, or GotoStatement whose matching label is outside the ForeachStatement.

For example, consider a class that is a container for two elements:

class Foo
{
uint[] array;

int opApply(scope int delegate(ref uint) dg)
{
foreach (e; array)
{
int result = dg(e);
if (result)
return result;
}
return 0;
}
}

void main()
{
import std.stdio;

Foo a = new Foo();
a.array = [73, 82, 2, 9];

foreach (uint u; a)
{
if (u < 5)
break;

writeln(u);
}
}

This would print:

73
82

The scope storage class on the dg parameter means that the delegate does not escape the scope of the opApply function (an example would be assigning dg to a global variable). If it cannot be statically guaranteed that dg does not escape, a closure may be allocated for it on the heap instead of the stack.
Best Practices: Annotate delegate parameters to opApply functions with scope when possible.

Important: If opApply catches any exceptions, ensure that those exceptions did not originate from the delegate passed to opApply. The user would expect exceptions thrown from a foreach body to both terminate the loop, and propagate outside the foreach body.

Template opApply

opApply can also be a templated function, which will infer the types of parameters based on the ForeachStatement. For example:

struct S
{
import std.traits : ParameterTypeTuple;  // introspection template
import std.stdio;

int opApply(Dg)(scope Dg dg)
if (ParameterTypeTuple!Dg.length == 2) // foreach with 2 parameters
{
writeln(2);
return 0;
}

int opApply(Dg)(scope Dg dg)
if (ParameterTypeTuple!Dg.length == 3) // foreach with 3 parameters
{
writeln(3);
return 0;
}
}

void main()
{
foreach (int a, int b; S()) { }  // calls first opApply function
foreach (int a, int b, float c; S()) { }  // calls second opApply function
}

Foreach over Structs and Classes with Ranges

If the ForeachAggregate is a struct or class object, but opApply does not exist, then foreach iteration will use range primitives. These primitives produce a series of elements.

The following properties and methods must be defined for the ForeachAggregate:

Foreach Range Properties

Property

Purpose

.empty

returns true if no more elements

.front

return the leftmost element of the range

Foreach Range Methods

Method

Purpose

.popFront()

move the left edge of the range right by one

A temporary variable is used for iteration, which is initialized from the ForeachAggregate. The statement:

foreach (e; range) { ... }

translates to:

for (auto __r = range; !__r.empty; __r.popFront())
{
auto e = __r.front;
...
}

Similarly, for foreach_reverse when opApplyReverse is not defined, range primitives will be used. The following properties and methods must be defined for the ForeachAggregate:

Foreach_reverse Range Properties

Property

Purpose

.empty

returns true if no more elements

.back

return the rightmost element of the range

Foreach_reverse Range Methods

Method

Purpose

.popBack()

move the right edge of the range left by one

The statement:

foreach_reverse (e; range) { ... }

translates to:

for (auto __r = range; !__r.empty; __r.popBack())
{
auto e = __r.back;
...
}

The ForeachType variable may be declared with ref when front (or back for foreach_reverse) is a Ref Function:

struct R
{
int[] a;
// range primitives:
bool empty() => a.length == 0;
ref front() => a[0];
void popFront() { a = a[1..$]; }
}

R r = R([1, 2, 3]);
foreach (ref e; r)
e *= 2;

assert(r.a == [2, 4, 6]);

Example with a linked list:

struct Node
{
int i;
Node* next;
}

// range
struct List
{
Node* node;
bool empty() { return node == null; }
ref int front() { return node.i; }
void popFront() { node = node.next; }
}

void main()
{
import std.stdio;
auto n = new Node(1, new Node(2, null));
auto r = List(n);

foreach (e; r)
{
writeln(e);
}
}

Multiple Element Values

Multiple loop variables are allowed if the front property returns a type that expands to a value sequence whose length matches the number of variables. Each variable is assigned to the corresponding value in the sequence. If there is only one variable, it will be a value sequence.

import std.stdio;

// simple version of std.typecons.Tuple
struct Tuple(Types...) // takes a TypeSeq
{
Types items; // ValueSeq
alias items this; // decay to a value sequence
}

string s = "hello";

struct TupleRange
{
size_t i;
auto front() => Tuple!(size_t, char)(i, s[i]);
bool empty() => i >= s.length;
void popFront() { i++; }
}

void main()
{
// Tuple destructuring
foreach (i, c; TupleRange())
{
writeln(i, ": ", c);
}
writeln();
// Tuple variable
foreach (tup; TupleRange())
{
writefln("(%s, %s)", tup[0], tup[1]);
}
}

See also: std.typecons.Tuple.

Foreach over Delegates

If ForeachAggregate is a delegate, the type signature and behavior of the delegate is the same as for opApply. This enables many different named looping strategies to coexist in the same class or struct.

The delegate can generate the elements on the fly:

// Custom loop implementation, that iterates over powers of 2 with
// alternating sign. The foreach loop body is passed in dg.
int myLoop(scope int delegate(int) dg)
{
for (int z = 1; z < 128; z *= -2)
{
auto ret = dg(z);

// If the loop body contains a break, ret will be non-zero.
if (ret != 0)
return ret;
}
return 0;
}

// Append each value in the iteration to an array
int[] result;
foreach (x; &myLoop)
{
result ~= x;
}
assert(result == [1, -2, 4, -8, 16, -32, 64, -128]);

foreach_reverse with a delegate is an error.

Foreach over Sequences

If the aggregate expression is a sequence, the loop body is statically expanded once for each element. This is similar to a StaticForeachStatement on a sequence, though there is a separate scope for each expansion.

There can be one or two iteration symbols declared. If one, then it's an element symbol representing each element in the sequence in turn.

- If the sequence is a TypeSeq, the element symbol is set to each type in turn.

- If the sequence is a ValueSeq, the element symbol is set to each value in turn. If the type of the element symbol is given, it must be compatible with the type of every sequence element. If no type is given, the type of the element symbol will match the type of each sequence element, which may change between elements.

If there are two symbols declared, the first is the index and the second is the element. The index symbol must be of int, uint, long or ulong type (if specified), it cannot be ref, and it is set to the index of each sequence element.

Example:

import std.meta : AliasSeq;

void main()
{
alias Seq = AliasSeq!(int, "literal", main);

foreach (int i, sym; Seq)
{
pragma(msg, i, ": ", sym.stringof);
}
}

Output:

0: int
1: "literal"
2: main()

Example:

void fun()
{
import std.meta : AliasSeq;

alias values = AliasSeq!(7.4, "hi", [2,5]);

foreach (sym; values)
{
pragma(msg, sym, " has type ", typeof(sym));
}
}

Output:

7.4 has type double
hi has type string
[2, 5] has type int[]

Symbol Kinds

Each ForeachType declaration (without alias/enum) will attempt to generate:

- Compile-time symbols for values known at compile-time

- Runtime variables for elements with values not known at compile-time

- Symbolic aliases for elements that don't have values (e.g types)

alias or enum can be specified in a ForeachType:

- The alias storage class will create a symbolic alias

- The enum storage class will create a compile-time constant

void main()
{
import std.meta : AliasSeq;

int i = 1;

// without `alias`, `a` would be a separate variable initialized from `i`
foreach (alias a; AliasSeq!(i, i))
{
a++;
}
assert(i == 3);

foreach (e; AliasSeq!(i, 1))
{
static if (__traits(compiles, &e))
assert(e == 3); // first e is a runtime variable
else
static assert(e == 1); // second e is a compile-time constant
}
}

Foreach Ref Parameters

ref can be used to modify the elements of the ForeachAggregate. This works for containers that expose lvalue elements, and lvalue sequences.

uint[2] a = [7, 8];

foreach (ref u; a)
{
u++;
}
foreach (u; a)
{
writeln(u);
}

which would print:

8
9

ref cannot be applied to an array index variable.

Foreach Restrictions

The aggregate itself must not be resized, reallocated, free'd, reassigned or destructed while foreach is iterating over the elements.

int[] a = [1, 2, 3];
auto fun = { a ~= 4; };

foreach (int v; a)
{
// resizing is unspecified!
fun();
a ~= 4;
a.length += 10;

// reallocating is unspecified!
a.reserve(10);

// reassigning is unspecified!
a = null;
a = [5, 6];
}
a ~= 4;   // OK
a = null; // OK

auto aa = [1: 1, 2: 2];

foreach (v; aa)
{
aa[3] = 3; // unspecified resize
aa.rehash; // unspecified reallocation
aa = [4: 4]; // unspecified reassign
}
aa[3] = 3; // OK
aa = null; // OK

Note: Resizing or reassigning a dynamic or associative array during foreach is still @safe.

Foreach Range Statement

A foreach range statement loops over the specified range.

RangeForeach:
Foreach ( ForeachType ; LwrExpression .. UprExpression )

LwrExpression:
Expression

UprExpression:
Expression

ForeachRangeStatement:
RangeForeach ScopeStatement

ForeachType declares a variable with either an explicit type, or a common type inferred from LwrExpression and UprExpression. The ScopeStatement is then executed n times, where n is the result of UprExpression - LwrExpression. If UprExpression is less than or equal to LwrExpression, the ScopeStatement is not executed.

If Foreach is foreach, then the variable is set to LwrExpression, then incremented at the end of each iteration. If Foreach is foreach_reverse, then the variable is set to UprExpression, then decremented before each iteration. LwrExpression and UprExpression are each evaluated exactly once, regardless of how many times the ScopeStatement is executed.

import std.stdio;

int foo()
{
write("foo");
return 10;
}

void main()
{
foreach (i; 0 .. foo())
{
write(i);
}
}

prints:

foo0123456789

Switch Statement
A switch statement goes to one of a collection of case statements depending on the value of the switch expression.

SwitchStatement:
switch ( IfCondition ) ScopeStatement

CaseStatement:
case ArgumentList : ScopeStatementListopt

DefaultStatement:
default : ScopeStatementListopt

ScopeStatementList:
StatementListNoCaseNoDefault

StatementListNoCaseNoDefault:
StatementNoCaseNoDefault
StatementNoCaseNoDefault StatementListNoCaseNoDefault

StatementNoCaseNoDefault:
EmptyStatement
NonEmptyStatementNoCaseNoDefault
ScopeBlockStatement

The Expression from the IfCondition is evaluated. If the type of the Expression is an enum, it is (recursively) converted to its EnumBaseType. Then, if the type is an integral, the Expression undergoes Integer Promotions. Then, the type of the Expression must be either an integral or a static or dynamic array of char, wchar, or dchar.

If an Identifier = prefix is provided, a variable is declared with that name, initialized to the value and type of the Expression. Its scope extends from when it is initialized to the end of the ScopeStatement.

If TypeCtors and/or a specific type is provided for Identifier, those are used for the variable declaration which is initialized by an implicit conversion from the value of the Expression.

The resulting value is compared against each of the case expressions. If there is a match, the corresponding case statement is transferred to.

The case expressions in ArgumentList are a comma separated list of expressions. Each expression must evaluate to a compile-time value or array, or a runtime initialized const or immutable variable of integral type. Each expression must be implicitly convertible to the type of the switch Expression.

Compile-time case values must all be distinct. Const or immutable runtime variables must all have different names. If two case expressions share a value, the first case statement with that value gets control.

The ScopeStatementList introduces a new scope.

A matching break statement will exit the switch BlockStatement.

A switch statement must have exactly one DefaultStatement. If none of the case expressions match, control is transferred to the default statement.

Rationale: This makes it clear that all possible cases are intentionally handled. See also: final switch.

foreach (i; 2 .. 10)
{
bool prime;

switch (i)
{
case 2, 3, 5, 7:
prime = true;
break;
default:
prime = false;
}
writeln(i, ": ", prime);
}

Case statements and default statements associated with the switch can be nested within block statements; they do not have to be in the outermost block. For example, this is allowed:

switch (i)
{
case 1:
{
case 2:
}
i++;
break;
default:
}

Implementation Note: The compiler's code generator may assume that the case statements are sorted by frequency of use, with the most frequent appearing first and the least frequent last. Although this is irrelevant as far as program correctness is concerned, it is of performance interest.

Case Range Statement

CaseRangeStatement:
case FirstExp : .. case LastExp : ScopeStatementListopt

FirstExp:
AssignExpression

LastExp:
AssignExpression

A CaseRangeStatement is a shorthand for listing a series of case statements from FirstExp to LastExp, inclusive.

case 1: .. case 3:

The above is equivalent to:

case 1, 2, 3:

No Implicit Fall-Through

A ScopeStatementList must either be empty, the last one in the switch, or be ended by:

- A ContinueStatement, BreakStatement, ReturnStatement, or GotoStatement

- Evaluating an assert(0) expression

- Evaluating an expression of type noreturn

Simple forms of the last 2 cases above can be recognized by the compiler, but not all.

uint i;
string message;

switch (i)
{
case 1:
message = "one";
// ERROR: implicit fall-through
case 2:
// valid: the body is empty
default:
message = "2 or more";
// valid: no more case statements
}

goto case; can be used for explicit fall-through to the next CaseStatement:

string message;

foreach (i; 1..5)
{
switch (i)
{
default:    // valid: ends with `noreturn` expression evaluation
throw new Exception("unknown number");

case 3:     // valid: ends with 'break' (break out of the 'switch' only)
message ~= "three";
break;

case 4:     // valid: ends with 'continue' (continue the enclosing loop)
message ~= "four";
continue; // don't append a comma

case 1:     // valid: ends with 'goto' (explicit fall-through to next case.)
message ~= ">";
goto case;

case 2:     // valid: this is the last case in the switch statement.
message ~= "one or two";
}
message ~= ", ";
}
writeln(message);

goto also supports jumping to a specific case or to the default case statement.

String Switch

Strings can be used in switch expressions. For example:

string name;
...
switch (name)
{
case "fred":
case "sally":
...
}

For applications like command line switch processing, this can lead to much more straightforward code, being clearer and less error prone. char, wchar and dchar strings are allowed.

Final Switch Statement

FinalSwitchStatement:
final switch ( IfCondition ) ScopeStatement

A final switch statement is just like a switch statement, except that:

- No DefaultStatement is allowed.

- No CaseRangeStatements are allowed.

- If the switch Expression is of enum type, all the enum members must appear in the CaseStatements.

- The case expressions cannot evaluate to a run time initialized value.

Implementation Defined: If the Expression value does not match any of the CaseRangeStatements, whether that is diagnosed at compile time or at runtime.

Continue Statement

ContinueStatement:
continue Identifieropt ;

continue aborts the current iteration of its innermost enclosing loop statement, and starts the next iteration. If the enclosing loop is a for statement, its Increment clause is executed.

string[] words = ["OK", "just", "longer", "words", "now"];

foreach (w; words)
{
if (w.length < 4)
continue; // skip writeln

writeln(w);
}

Output:

just
longer
words

If continue is followed by Identifier, the Identifier must be the label of an enclosing loop statement, and the next iteration of that loop is executed. It is an error if there is no such statement.

outer:
foreach (item; list)
{
// try 3 times
foreach (i; 0 .. 3)
{
if (item.buy())
continue outer; // skip to next item

log("attempt failed");
}
}

Any intervening finally clauses are executed, and any intervening synchronization objects are released.

Note: If a finally clause executes a throw out of the finally clause, the continue target is never reached.

Break Statement

BreakStatement:
break Identifieropt ;

break exits the innermost enclosing loop or switch statement, resuming execution at the statement following it.

const n = 55;

// find the smallest factor of n
foreach (i; 2 .. n)
{
writeln("Trying: ", i);
if (n % i == 0)
{
writeln("smallest factor is ", i);
break; // stop looking
}
}
writeln("finished");

Output:

Trying: 2
Trying: 3
Trying: 4
Trying: 5
smallest factor is 5
finished

If break is followed by Identifier, the Identifier must be the label of an enclosing loop or switch statement, and that statement is exited. It is an error if there is no such statement.

// display messages cyclically until the shop is closed
outer:
while (true)
{
foreach (msg; messages)
{
if (shop.isClosed())
break outer; // end the while loop

display(msg);
}
}
display("opens at 9am");

Any intervening finally clauses are executed, and any intervening synchronization objects are released.

Note: If a finally clause executes a throw out of the finally clause, the break target is never reached.

Return Statement

ReturnStatement:
return Expressionopt ;

return exits the current function and supplies its return value.

Expression is required if the function specifies a return type that is not void. The Expression is implicitly converted to the function return type.

An Expression of type void is allowed if the function specifies a void return type. The Expression will be evaluated, but nothing will be returned. This is useful in generic programming.

Before the function actually returns, any objects with scope storage duration are destroyed, any enclosing finally clauses are executed, any scope(exit) statements are executed, any scope(success) statements are executed, and any enclosing synchronization objects are released.

The function will not return if any enclosing finally clause does a return, goto or throw that exits the finally clause.

If there is an out postcondition, that postcondition is executed after the Expression is evaluated and before the function actually returns.

int foo(int x)
{
return x + 3;
}

Goto Statement

GotoStatement:
goto Identifier ;
goto default ;
goto case ;
goto case Expression ;

goto transfers to the statement labeled with Identifier.

int x = 1;
if (x)
goto L1;

x = 3;
L1:
assert(x == 1);

The second form, goto default;, transfers to the innermost DefaultStatement of an enclosing SwitchStatement.

The third form, goto case;, transfers to the next CaseStatement of the innermost enclosing SwitchStatement.

The fourth form, goto case Expression;, transfers to the CaseStatement of the innermost enclosing SwitchStatement with a matching Expression.

int x = 5;

switch (x)
{
case 3:
writeln("case 3");
goto case;
case 4:
writeln("case 4");
goto default;
case 5:
writeln("case 5");
goto case 4;
default:
writeln("default");
break;
}

Any intervening finally clauses are executed, along with releasing any intervening synchronization mutexes.

It is illegal for a GotoStatement to be used to skip initializations within its containing scope.

Best Practices: Prefer using a higher-level control-flow construct or a labelled BreakStatement/ContinueStatement rather than the goto Identifier; form.

With Statement

The with statement is a way to simplify repeated references to the same object.

WithStatement:
with ( Expression ) ScopeStatement
with ( Symbol ) ScopeStatement
with ( TemplateInstance ) ScopeStatement

where Expression evaluates to one of:

- a class reference

- a struct instance

- an enum instance

- a pointer to one of the above

Within the with body the referenced object is searched first for identifier symbols.

enum E { A, B }

void test(E e)
{
with (e)       // affects the switch statement
switch (e)
{
case A:    // no need for E.A
case B:
default:
break;
}
}

Below, if ident is a member of the type of expression, the WithStatement:

with (expression)
{
...
ident;
}

is semantically equivalent to:

(auto ref tmp)
{
...
tmp.ident;
}(expression);

Note that Expression only gets evaluated once and is not copied. The with statement does not change what this or super refer to.

For Symbol which is a scope or TemplateInstance, the corresponding scope is searched when looking up symbols. For example:

struct Foo
{
alias Y = int;
}
...
Y y;        // error, Y undefined
with (Foo)
{
Y y;    // same as Foo.Y y;
}

Use of with object symbols that shadow local symbols with the same identifier are not allowed. This is to reduce the risk of inadvertent breakage of with statements when new members are added to the object declaration.

struct S
{
float x;
}

void main()
{
int x;
S s;
with (s)
{
x++;  // error, shadows the int x declaration
}
}

In nested WithStatements, the inner-most scope takes precedence. If a symbol cannot be resolved at the inner-most scope, resolution is forwarded incrementally up the scope hierarchy.

import std.stdio;

struct Foo
{
void f() { writeln("Foo.f"); }
}

struct Bar
{
void f() { writeln("Bar.f"); }
}

struct Baz
{
// f() is not implemented
}

void f()
{
writeln("f");
}

void main()
{
Foo foo;
Bar bar;
Baz baz;

f();               // prints "f"

with(foo)
{
f();           // prints "Foo.f"

with(bar)
{
f();       // prints "Bar.f"

with(baz)
{
f();   // prints "Bar.f".  `Baz` does not implement `f()` so
// resolution is forwarded to `with(bar)`'s scope
}
}
with(baz)
{
f();       // prints "Foo.f".  `Baz` does not implement `f()` so
// resolution is forwarded to `with(foo)`'s scope
}
}
with(baz)
{
f();           // prints "f".  `Baz` does not implement `f()` so
// resolution is forwarded to `main`'s scope. `f()` is
// not implemented in `main`'s scope, so resolution is
// subsequently forward to module scope.
}
}

Synchronized Statement

The synchronized statement wraps a statement with mutex locking and unlocking to synchronize access among multiple threads.

SynchronizedStatement:
synchronized ScopeStatement
synchronized ( Expression ) ScopeStatement

A synchronized statement without Expression allows only one thread at a time to execute ScopeStatement by locking a mutex. A global mutex is created, one per synchronized statement. Different synchronized statements will have different global mutexes.

If there is an Expression, it must evaluate to either an Object or an instance of an interface, in which case it is cast to the Object instance that implemented that interface. The mutex used is specific to that Object instance, and is shared by all synchronized statements referring to that instance. If the object's mutex is already locked when reaching the synchronized statement, it will block every thread until that mutex is unlocked by other code.

void work();

void f(Object o)
{
synchronized (o) work();
}

void g(Object o)
{
synchronized (o) work();
}

If f and g are called by different threads but with the same argument, the work calls cannot execute simultaneously. If the (o) part of the synchronized statements is removed in one or both functions, then both work calls could execute simultaneously, because they would be protected by different mutexes.

The synchronization gets released even if ScopeStatement terminates with an exception, goto, or return.

This implements a standard critical section.

Synchronized statements support recursive locking; that is, a function wrapped in synchronized is allowed to recursively call itself and the behavior will be as expected: The mutex will be locked and unlocked as many times as there is recursion.

See also synchronized classes.

Try Statement

Exception handling is done with the try-catch-finally statement.

TryStatement:
try ScopeStatement Catches
try ScopeStatement Catches FinallyStatement
try ScopeStatement FinallyStatement

Catches:
Catch
Catch Catches

Catch:
catch ( CatchParameter ) NoScopeNonEmptyStatement

CatchParameter:
BasicType Identifieropt

FinallyStatement:
finally NoScopeNonEmptyStatement

CatchParameter declares a variable v of type T, where T is Throwable or derived from Throwable. v is initialized by the throw expression if T is of the same type or a base class of the throw expression. The catch clause will be executed if the exception object is of type T or derived from T.

If just type T is given and no variable v, then the catch clause is still executed.

It is an error if any CatchParameter type T1 hides a subsequent Catch with type T2, i.e. it is an error if T1 is the same type as or a base class of T2.

The FinallyStatement is always executed, whether the try ScopeStatement exits with a goto, break, continue, return, exception, or fall-through.

If an exception is raised in the FinallyStatement and is not caught before the original exception is caught, it is chained to the previous exception via the next member of Throwable. Note that, in contrast to most other programming languages, the new exception does not replace the original exception. Instead, later exceptions are regarded as 'collateral damage' caused by the first exception. The original exception must be caught, and this results in the capture of the entire chain.

Thrown objects derived from the Error class are treated differently. They bypass the normal chaining mechanism, such that the chain can only be caught by catching the first Error. In addition to the list of subsequent exceptions, Error also contains a pointer that points to the original exception (the head of the chain) if a bypass occurred, so that the entire exception history is retained.

import std.stdio;

int main()
{
try
{
try
{
throw new Exception("first");
}
finally
{
writeln("finally");
throw new Exception("second");
}
}
catch (Exception e)
{
writefln("catch %s", e.msg);
}
writeln("done");
return 0;
}

prints:

finally
catch first
done

A FinallyStatement may not exit with a goto, break, continue, or return; nor may it be entered with a goto.

A FinallyStatement may not contain any Catches. This restriction may be relaxed in future versions.

Scope Guard Statement

ScopeGuardStatement:
scope ( exit ) NonEmptyOrScopeBlockStatement
scope ( success ) NonEmptyOrScopeBlockStatement
scope ( failure ) NonEmptyOrScopeBlockStatement

The ScopeGuardStatement executes NonEmptyOrScopeBlockStatement at the close of the current scope, rather than at the point where the ScopeGuardStatement appears. scope(exit) executes NonEmptyOrScopeBlockStatement when the scope exits normally or when it exits due to exception unwinding. scope(failure) executes NonEmptyOrScopeBlockStatement when the scope exits due to exception unwinding. scope(success) executes NonEmptyOrScopeBlockStatement when the scope exits normally.

If there are multiple ScopeGuardStatements in a scope, they will be executed in the reverse lexical order in which they appear. If any scope instances are to be destroyed upon the close of the scope, their destructions will be interleaved with the ScopeGuardStatements in the reverse lexical order in which they appear.

write("1");
{
write("2");
scope(exit) write("3");
scope(exit) write("4");
write("5");
}
writeln();

writes:

12543

{
scope(exit) write("1");
scope(success) write("2");
scope(exit) write("3");
scope(success) write("4");
}
writeln();

writes:

4321

struct Foo
{
this(string s) { write(s); }
~this() { write("1"); }
}

try
{
scope(exit) write("2");
scope(success) write("3");
Foo f = Foo("0");
scope(failure) write("4");
throw new Exception("msg");
scope(exit) write("5");
scope(success) write("6");
scope(failure) write("7");
}
catch (Exception e)
{
}
writeln();

writes:

0412

A scope(exit) or scope(success) statement may not exit with a throw, goto, break, continue, or return; nor may it be entered with a goto. A scope(failure) statement may not exit with a return.

import std.stdio;

int foo()
{
scope(exit) writeln("Inside foo()");
return bar();
}

int bar()
{
writeln("Inside bar()");
return 0;
}

int main()
{
foo();
return 0;
}

writes:

Inside bar()
Inside foo()

Catching C++ Class Objects

On many platforms, catching C++ class objects is supported. Catching C++ objects and D objects cannot both be done in the same TryStatement. Upon exit from the Catch, any destructors for the C++ object will be run and the storage used for it reclaimed. C++ objects cannot be caught in @safe code.

Asm Statement

Inline assembler is supported with the asm statement:

AsmStatement:
asm FunctionAttributesopt { AsmInstructionListopt }

AsmInstructionList:
AsmInstruction ;
AsmInstruction ; AsmInstructionList

An asm statement enables the direct use of assembly language instructions. This makes it easy to obtain direct access to special CPU features without resorting to an external assembler. The D compiler will take care of the function calling conventions, stack setup, etc.

The format of the instructions is, of course, highly dependent on the native instruction set of the target CPU, and so is implementation defined. But, the format will follow the following conventions:

- It must use the same tokens as the D language uses.

- The comment form must match the D language comments.

- Asm instructions are terminated by a ;, not by an end of line.

These rules exist to ensure that D source code can be tokenized independently of syntactic or semantic analysis.
For example, for the Intel Pentium:

int x = 3;
asm
{
mov EAX,x; // load x and put it in register EAX
}

Inline assembler can be used to access hardware directly:

int gethardware()
{
asm
{
mov EAX, dword ptr 0x1234;
}
}

For some D implementations, such as a translator from D to C, an inline assembler makes no sense, and need not be implemented. The version statement can be used to account for this:

version (D_InlineAsm_X86)
{
asm
{
...
}
}
else
{
/* ... some workaround ... */
}

Semantically consecutive AsmStatements shall not have any other instructions (such as register save or restores) inserted between them by the compiler.

Pragma Statement

See PragmaStatement
.
Mixin Statement

MixinStatement:
mixin ( ArgumentList ) ;

Each AssignExpression in the ArgumentList is evaluated at compile time, and the result must be representable as a string. The resulting strings are concatenated to form a string. The text contents of the string must be compilable as a valid StatementList, and is compiled as such.

import std.stdio;

void main()
{
int i = 0;
mixin("
int x = 3;
for (; i < 3; i++)
writeln(x + i, i);
");    // ok

enum s = "int y;";
mixin(s);  // ok
y = 4;     // ok, mixin declared y

string t = "y = 3;";
//mixin(t);  // error, t is not evaluatable at compile time
//mixin("y =") 4; // error, string must be complete statement

mixin("y =" ~ "4;");  // ok
mixin("y =", 2+2, ";");  // ok
}

)

Arrays

Kinds

There are four kinds of arrays:

Kinds of Arrays

Syntax

Description

type[integer]

Static arrays

type[]

Dynamic arrays

type*

Pointer arrays

type[type]

Associative arrays

Static Arrays

int[3] s;

Static arrays have a length fixed at compile time.

The total size of a static array cannot exceed 16Mb.

A static array with a dimension of 0 is allowed, but no space is allocated for it.

Static arrays are value types. They are passed to and returned by functions by value.

Best Practices:

1. Use dynamic arrays for larger arrays.

2. Because static arrays are passed to functions by value, a larger array can consume a lot of stack space. Use dynamic arrays instead.

Dynamic Arrays

int[] a;

Dynamic arrays consist of a length and a pointer to the array data. Multiple dynamic arrays can share all or parts of the array data.

Best Practices:

1. Use dynamic arrays instead of pointer arrays as much as practical. Indexing of dynamic arrays are bounds checked, avoiding buffer underflow and overflow problems.

Pointer Arrays

int* p;

A pointer can manipulate a block of multiple contiguous values in memory. Accessing more than one value cannot be @safe as it requires pointer arithmetic. This is supported for interfacing with C and for specialized systems work. A pointer has no length associated with it, so there is no way for the compiler or runtime to do bounds checking, etc., on it.

Best Practices: Most conventional uses for pointers can be replaced with dynamic arrays, ref and out parameters, and reference types.

Array Declarations

Declarations appear before the identifier being declared and read right to left, so:

int[] a;      // dynamic array of ints
int[4][3] b;  // array of 3 arrays of 4 ints each
int[][5] c;   // array of 5 dynamic arrays of ints.
int*[]*[3] d; // array of 3 pointers to dynamic arrays of pointers to ints
int[]* e;     // pointer to dynamic array of ints

Array Initializers & Literals

An ArrayInitializer initializes a dynamic or static array:

auto a1 = [1,2,3];  // type is int[], with elements 1, 2, and 3
auto a2 = [1u,2,3]; // type is uint[], with elements 1u, 2u, and 3u
int[2] a3 = [1,2];  // type is int[2], with elements 1, and 2

An array literal is an expression:

void f(int[] a);

f([1, 2]); // pass 2 elements
f([]); // pass an empty array

Array Comparison

For static and dynamic arrays:

- The .length property will give the number of elements in the array.

- The .ptr property will give a pointer to the first element in the array.

- a == b compares both the array lengths and array elements.

- a is b compares each .ptr and each .length. Note that this is deprecated for static arrays because those are value types, so checking identity should not compare the .ptr fields.

int[3] s = [1, 2, 3];
int[] a = [1, 2, 3];

assert(s.length == 3);
assert(a.length == 3);
assert(s == a);
assert(s.ptr != a.ptr); // here `a` is not a slice of `s`
assert(s !is a);

See also: RelExpression.

Array Assignment

There are two broad kinds of operations to do on dynamic arrays and pointer arrays - those affecting the handle to the array, and those affecting the contents of the array. Assignment only affects the handle for these types.

int* p;
int[3] s;
int[] a;

p = s.ptr; // p points to the first element of the array s.
assert(p !is null);
p = a.ptr;
assert(p is null);

// error, since the length of the array pointed to by p is unknown
//s = p;

//a = p;   // error, length unknown
a = s;     // a points to the elements of s
assert(a.ptr == s.ptr);

int[] b = [1, 2, 3];
a = b;     // a points to the same array as b does
assert(a.ptr == b.ptr);
assert(a is b);

Note: The two error lines above can be made to copy elements using pointer slicing, so that the number of elements to copy is then known.

Static Array Assignment

A static array can be assigned from a dynamic array - the data is copied. If the lengths do not match at runtime, a RangeError is thrown:

int[3] s;
int[] a;

//s = [1, 2]; // error
s = [1, 2, 3]; // OK
//s = [1, 2, 3, 4]; // error

a = [4, 5, 6];
s = a; // OK
assert(s == a);
assert(s.ptr != a.ptr);

a = [1, 2];
//s = a; // RangeError, length mismatch

a = s;
assert(a.ptr == s.ptr);
//s = a; // RangeError, overlap

The dynamic array data must not overlap with the static array memory. See also Copying.

Indexing

Indexing allows access to an element of an array:

auto a = [1,2,3];
assert(a[0] == 1);
assert(a[2] == 3);
a[2] = 4;
assert(a[2] == 4);
assert(a == [1,2,4]);
//writeln(a[3]); // runtime error (unless bounds checks turned off)

int[2] b = [1,2];
assert(b[1] == 2);
//writeln(b[2]); // compile-time error, index out of bounds

Dynamic and static array indexing is bounds checked by default.

See also IndexOperation.

Pointer Arithmetic

A pointer can also be indexed, but no bounds checks are done. Unlike arrays, a pointer value can also be used in certain arithmetic expressions to produce another pointer:

int[] a = [1,2,3];
int* p = a.ptr;

p[2] = 4;
assert(a[2] == 4);
writeln(p[3]); // undefined behaviour

assert(p == &a[0]);
p++; // point to a[1]
assert(*p == 2);

See AddExpression for details.

Slicing

Slicing an array means to specify a subarray of it. This is done by supplying two index expressions. The elements from the start index up until the end index are selected. Any item at the end index is not included.

An array slice does not copy the data, it is only another reference to it. Slicing produces a dynamic array.

int[3] a = [4, 5, 6]; // static array of 3 ints
int[] b;

b = a[1..3]; // a[1..3] is a 2 element dynamic array consisting of
// a[1] and a[2]
assert(b == [5, 6]);
assert(b.ptr == a.ptr + 1);

a[2] = 3;
assert(b == [5, 3]);

b = b[1..2];
assert(b == [3]);

Each index in the slice expression is bounds checked by default.

Expression[] is shorthand for a slice of the entire array.

Slicing is not only handy for referring to parts of other arrays, but for converting pointers into bounds-checked arrays:

int[10] a = [ 1,2,3,4,5,6,7,8,9,10 ];

int* p = &a[2];
writeln(p[7]);      // 10
writeln(p[8]);      // undefined behaviour

int[] b = p[0..8];  // convert pointer elements to dynamic array
assert(b is a[2..10]);
writeln(b);
writeln(b[7]);      // 10
//writeln(b[8]);    // runtime error (unless bounds checks turned off)

See also SliceOperation.

Array Length

When indexing or slicing a static or dynamic array, the symbol $ represents the length of the array.

int[4] foo;
int[] bar;

// These assignments are equivalent:
bar = foo;
bar = foo[];
bar = foo[0 .. 4];
bar = foo[0 .. $];
bar = foo[0 .. foo.length];
assert(bar.length == 4);

int* p = foo.ptr; // a pointer has no length property
bar = p[0 .. 4]; // OK
//bar = p[0 .. $]; // error, '$' is not defined, since p is not an array

int i;
//i = foo[0]+$; // error, '$' is not defined, out of scope of [ ]
i = bar[$-1]; // retrieves last element of the array

Array Copying

When a slice expression is the left-hand side of an assignment expression, it means that the contents of the slice are the target of the assignment rather than an array reference. The right-hand side must implicitly convert to an array type.

int[3] s, t;
int[] a;

s = t;             // the 3 elements of t are copied into s
s[] = t;           // the 3 elements of t are copied into s
s[] = t[];         // the 3 elements of t are copied into s
s[1..2] = t[0..1]; // same as s[1] = t[0]
s[0..2] = t[1..3]; // same as s[0] = t[1], s[1] = t[2]
//s[0..4] = t[0..4]; // error, only 3 elements in s and t
//s[0..2] = t;       // error, operands have different lengths

a = [1, 2];
s[0..2] = a;
assert(s == [1, 2, 0]);

//a[] = s; // RangeError, lengths don't match
a[0..2] = s[1..3];
assert(a == [2, 0]);

If the number of elements does not match at runtime, a RangeError is thrown.

Rationale: Indexing past the end of a slice is a programming error.

Overlapping Copying

Overlapping copies are an error:

void main()
{
int[3] s;

s[0..2] = s[1..3]; // error, overlapping copy
s[1..3] = s[0..2]; // error, overlapping copy
}

Disallowing overlapping makes it possible for more aggressive parallel code optimizations than possible with the serial semantics of C.

If overlapping is required, use std.algorithm.mutation.copy:

import std.algorithm;
int[] s = [1, 2, 3, 4];

copy(s[1..3], s[0..2]);
assert(s == [2, 3, 3, 4]);

Array Filling

If a slice operator appears as the left-hand side of an assignment expression, and the type of the right-hand side is the same as the element type of the left-hand side, then each element of the slice expression will be assigned to the value on the right-hand side.

int[3] s;
int[] a;
int* p;

s[] = 3;
assert(s == [3, 3, 3]);

a = s;
a[] = 1;
assert(s == [1, 1, 1]);

p = s.ptr;
p[0..2] = 2;
assert(s == [2, 2, 1]);

Array Concatenation

The binary operator ~ is the cat operator. It is used to concatenate arrays:

int[] a = [1, 2];
assert(a ~ 3 == [1, 2, 3]); // concatenate array with a single value

int[] b = a ~ [3, 4];
assert(b == [1, 2, 3, 4]); // concatenate two arrays

Many languages overload the + operator for concatenation. This confusingly leads to a dilemma - does:

"10" + 3 + 4

produce the number 17, the string "1034" or the string "107" as the result? It isn't obvious, and the language designers wind up carefully writing rules to disambiguate it - rules that get incorrectly implemented, overlooked, forgotten, and ignored. It's much better to have + mean addition, and a separate operator to be array concatenation.

Concatenation always creates a copy of its operands, even if one of the operands is a 0 length array, so:

auto b = [7];
auto a = b;      // a refers to b
assert(a is b);

a = b ~ []; // a refers to a copy of b
assert(a !is b);
assert(a == b);

See also: is operator.

Array Appending

Similarly, the ~= operator means append, as in:

a ~= b; // a becomes the concatenation of a and b

Appending does not always create a copy, see setting dynamic array length for details.

Vector Operations

Many array operations can be expressed at a high level rather than as a loop. For example, the loop:

T[] a, b;
...
for (size_t i = 0; i < a.length; i++)
a[i] = b[i] + 4;

assigns to the elements of a the elements of b with 4 added to each. This can also be expressed in vector notation as:

T[] a, b;
...
a[] = b[] + 4;

A vector operation is indicated by the slice operator appearing as the left-hand side of an assignment or an op-assignment expression. The right-hand side can be certain combinations of:

- An array SliceOperation of the same length and type as the left-hand side

- A scalar expression of the same element type as the left-hand side

The following operations are supported:

- Unary: -, ~

- Add: +, -

- Mul: *, /, %,

- Bitwise: ^, &, |

- Pow: ^^

int[3] a = 0;
int[] b = [1, 2, 3];

a[] += 10 - (b[] ^^ 2);
assert(a == [9, 6, 1]);

Note: In particular, an expression using ConditionalExpression, logical expressions, CmpExpression, concatenation ~ or a function call is not a vector op.

The slice on the left and any slices on the right must not overlap. All operands are evaluated exactly once, even if the array slice has zero elements in it.

If the element type defines matching overloaded operators, those methods must be pure nothrow @nogc.

The order in which the array elements are computed is implementation defined, and may even occur in parallel. An application must not depend on this order.

Implementation Note: Many vector operations are expected to take advantage of any vector math instructions available on the target computer.

Rectangular Arrays

Experienced FORTRAN numerics programmers know that multidimensional "rectangular" arrays for things like matrix operations are much faster than trying to access them via pointers to pointers resulting from "array of pointers to array" semantics. For example, the D syntax:

double[][] matrix;

declares matrix as an array of pointers to arrays. (Dynamic arrays are implemented as pointers to the array data.) Since the arrays can have varying sizes (being dynamically sized), this is sometimes called "jagged" arrays. Even worse for optimizing the code, the array rows can sometimes point to each other! Fortunately, D static arrays, while using the same syntax, are implemented as a fixed rectangular layout in a contiguous block of memory:

import std.stdio : writeln;

double[6][3] matrix = 0; // Sets all elements to 0.

void main()
{
writeln(matrix); // [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]
}

Note that dimensions and indices appear in opposite orders. Dimensions in the declaration are read right to left whereas indices are read left to right:

import std.stdio : writeln;

void main()
{
double[6][3] matrix = 0;
matrix[2][5] = 3.14; // Assignment to bottom right element.
writeln(matrix); // [[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 3.14]]

static assert(!__traits(compiles, matrix[5][2])); // Array index out of bounds.
}

More information can be found at Dlang Wiki - Dense Multidimensional Arrays.

Array Properties

Static array properties are:

Static Array Properties

Property

Description

.init

Returns an array literal with each element of the literal being the .init property of the array element type.

.sizeof

Returns the array length multiplied by the number of bytes per array element.

.length

Returns the number of elements in the array. This is a fixed quantity for static arrays, known at compile-time. It is of type size_t.

.ptr

Returns a pointer to the first element of the array.

.dup

Create a dynamic array of the same size and copy the contents of the array into it. The copy will have any immutability or const stripped. If this conversion is invalid the call will not compile.

.idup

Create a dynamic array of the same size and copy the contents of the array into it. The copy is typed as being immutable. If this conversion is invalid the call will not compile.

.tupleof

Returns an lvalue sequence of each element in the array:

void foo(int, int, int) { /* ... */ }

int[3] ia = [1, 2, 3];
foo(ia.tupleof); // same as `foo(1, 2, 3);`

float[3] fa;
//fa = ia; // error
fa.tupleof = ia.tupleof;
assert(fa == [1F, 2F, 3F]);

Dynamic array properties are:

Dynamic Array Properties

Property

Description

.init

Returns [].

.sizeof

Returns the size of the dynamic array reference, which is 8 in 32-bit builds and 16 on 64-bit builds.

.length

Get/set number of elements in the array. It is of type size_t.

.capacity

Returns the length an array can grow to without reallocating.

.ptr

Returns a pointer to the first element of the array.

.dup

Create a dynamic array of the same size and copy the contents of the array into it. The copy will have any immutability or const stripped. If this conversion is invalid the call will not compile.

.idup

Create a dynamic array of the same size and copy the contents of the array into it. The copy is typed as being immutable. If this conversion is invalid the call will not compile.

Examples:

int* p;
int[3] s;
int[] a;

size_t len;
//len = p.length;   // error, pointer has no length property
enum sl = s.length; // compile time constant
static assert(sl == 3);

len = a.length; // runtime value
assert(len == 0);

//a = p.dup; // error, length not known
a = s.dup;   // allocates an array of 3 elements, copies
// elements of `s` into it
assert(a == s[]);
assert(a.ptr != s.ptr);

int[] b = a.dup; // allocates a new array of `a.length` elements, copies
// elements of `a` into it
assert(b !is a); // b is an independent copy of a

.ptr Property

The .ptr property will give a pointer to the first element in a static or dynamic array. It may be a dangling pointer if the array length is zero. For this reason, .ptr is not accessible in @safe code.

A dynamic array's .ptr is default-initialized to null.

An array with zero length may have a non-null .ptr. That can occur:

- From slicing a dynamic array a with zero-width: a[$..$].

- For a zero-length static array.

int[] a;
assert(a.ptr is null);

int[0] z;
assert(z.ptr !is null);

Setting Dynamic Array Length

The .length property of a dynamic array can be set as the left-hand side of an = operator:

array.length = 7;

This causes the array to be reallocated in place, and the existing contents copied over to the new array. If the new array length is shorter, the array is not reallocated, and no data is copied. It is equivalent to slicing the array:

array = array[0..7];

If the new array length is longer, the array is reallocated if necessary, preserving the existing elements. The new elements are filled out with the default initializer.

Growing an Array

To maximize efficiency, the runtime always tries to resize the array in place to avoid extra copying. It will do a copy if the new size is larger and either:

- The array was not allocated by the GC.

- There is no spare capacity for the array.

- Resizing in place would overwrite valid data still accessible in another slice.

char[] a = new char[20];
char[] b = a[0..10];
char[] c = a[10..20];
char[] d = a;

b.length = 15; // always reallocates because extending in place would
// overwrite other data in a.
b[11] = 'x';   // a[11] and c[1] are not affected
assert(a[11] == char.init);

d.length = 1;
assert(d.ptr == a.ptr); // unchanged

d.length = 20; // also reallocates, because doing this will overwrite a and c
assert(d.ptr != a.ptr);

c.length = 12; // may reallocate in place if space allows, because nothing
// was allocated after c.
c[5] = 'y';    // may affect contents of a, but not b or d because those
// were reallocated.

a.length = 25; // This always reallocates because if c extended in place,
// then extending a would overwrite c.  If c didn't
// reallocate in place, it means there was not enough space,
// which will still be true for a.
a[15] = 'z';   // does not affect c, because either a or c has reallocated.

To guarantee copying behavior, use the .dup property to ensure a unique array that can be resized.

Note: These issues also apply to appending arrays with the ~= operator. Concatenation using the ~ operator is not affected since it always reallocates.

Resizing a dynamic array is a relatively expensive operation. So, while the following method of filling an array:

void fun()
{
int[] array;
while (1)
{
import core.stdc.stdio : getchar;
auto c = getchar;
if (!c)
break;
++array.length;
array[array.length - 1] = c;
}
}

will work, it will be inefficient. A more practical approach would be to minimize the number of resizes:

void fun()
{
int[] array;
array.length = 100;        // guess
int i;
for (i = 0; ; i++)
{
import core.stdc.stdio : getchar;
auto c = getchar;
if (!c)
break;
if (i == array.length)
array.length *= 2;
array[i] = c;
}
array.length = i;
}

Base selection of the initial size on expected common use cases, which can be determined by instrumenting the code, or simply using good judgement. For example, when gathering user input from the console - it's unlikely to be longer than 80.

capacity and reserve

The capacity property gives the maximum length a dynamic array can grow to without reallocating. The spare capacity for an array a is a.capacity - a.length. The capacity for a slice is zero:

- If it does not point to GC-allocated dynamic array memory.

- By default, when an element has been stored after the slice.

int[] a;
assert(a.capacity == 0);
a.length = 3; // may allocate spare capacity too
assert(a.capacity >= 3);

auto b = a[1..3];
assert(b.capacity >= 2); // either a or b can append into any spare capacity
b = a[0..2];
assert(b.capacity == 0);

Rationale: This behaviour helps prevent accidental overwriting of elements in another slice. It is also necessary to protect immutable elements from being overwritten.

The reserve function requests a minimum capacity for an array. It returns the new capacity, which may be more than requested. Any spare capacity can be used by the append operator or .length assignment.

int[] array;
const size_t cap = array.reserve(10); // request
assert(cap >= 10); // allocated may be more than request
assert(array.ptr != null);
assert(array.length == 0);

int[] copy = array;
assert(copy.capacity == cap); // array and copy have same capacity
array ~= [1, 2, 3, 4, 5]; // grow in place
assert(cap == array.capacity); // array memory was not reallocated
assert(copy.ptr == array.ptr);
assert(copy.capacity == 0);
copy ~= 0; // allocates a new array
assert(copy.ptr != array.ptr);
assert(array[0] == 1);

Above, copy's length remains zero but it points to the same memory allocated by the reserve call. Because array is then appended to, copy.ptr + 0 no longer points to unused memory - instead that is the address of array[0]. So copy.capacity will be zero to prevent any appending to copy from overwriting elements in array.

Note: The runtime uses the last element of a slice to track the start of the spare capacity for the memory allocation.

When an array with spare capacity has its length reduced, or is assigned a slice of itself that ends before the previous last element, the capacity will be zero.

The @system function assumeSafeAppend allows the capacity to be regained, but care must be taken not to overwrite immutable elements that may exist in another slice.

int[] a = [1, 2, 3];
a.length--;
assert(a.capacity == 0);
a.assumeSafeAppend();
assert(a.capacity >= 3);

Note: Accessing .capacity may require the runtime to acquire a global lock and perform a cache lookup.

Best Practices: Avoid intensive use of .capacity in performance-sensitive code. Instead, track the capacity locally when building an array via a unique reference. E.g. std.array.Appender.

Functions as Array Properties

See Uniform Function Call Syntax (UFCS).

Array Bounds Checking

It is an error to index an array with an index that is less than 0 or greater than or equal to the array length. If an index is out of bounds, an ArrayIndexError is thrown if detected at runtime, and an error is raised if detected at compile time. A program may not rely on array bounds checking happening, for example, the following program is incorrect:

void main()
{
import core.exception;
try
{
auto array = [1, 2];
for (auto i = 0; ; i++)
{
array[i] = 5;
}
}
catch (ArrayIndexError)
{
// terminate loop
}
}

The loop is correctly written:

void main()
{
auto array = [1, 2];
for (auto i = 0; i < array.length; i++)
{
array[i] = 5;
}
}

Implementation Note: Compilers should attempt to detect array bounds errors at compile time, for example:

int[3] foo;
int x = foo[3]; // error, out of bounds

Insertion of array bounds checking code at runtime should be turned on and off with a compile time switch.

Undefined Behavior: An out of bounds memory access will cause undefined behavior, therefore array bounds check is normally enabled in @safe functions. The runtime behavior is part of the language semantics.

See also Safe Functions.

Disabling Array Bounds Checking

Insertion of array bounds checking code at runtime may be turned off with a compiler switch -boundscheck.

If the bounds check in @system or @trusted code is disabled, the code correctness must still be guaranteed by the code author.

On the other hand, disabling the bounds check in @safe code will break the guaranteed memory safety by compiler. It's not recommended unless motivated by speed measurements.

Array Initialization

Default Initialization

- Pointers are initialized to null.

- Static array contents are initialized to the default initializer for the array element type.

- Dynamic arrays are initialized to having 0 elements and a null .ptr.

- Associative arrays are initialized to having 0 elements.

Length Initialization

The new expression can be used to allocate a dynamic array with a specified length by specifying its type and then using the (size) syntax:

int[] i = new int[](5);
i = new int[5]; // same allocation, alternate syntax
assert(i.length == 5);

int[][] j = new int[][](10, 5);
assert(j.length == 10);
assert(j[0].length == 5);

See NewExpression for details.

Void Initialization

Void initialization happens when the Initializer for an array declaration is void. For a static array, it means no initialization is done - i.e. the contents of the array will be undefined. This is most useful as an efficiency optimization.

int[256] arr = void; // elements of `arr` have undefined bit-patterns

Best Practices: Void initializations are an advanced technique and should only be used when profiling indicates that it matters.

To void initialise the elements of a dynamic array use std.array.uninitializedArray.

Array Initializers

ArrayInitializer:
[ ArrayElementInitializersopt ]

ArrayElementInitializers:
ArrayElementInitializer
ArrayElementInitializer ,
ArrayElementInitializer , ArrayElementInitializers

ArrayElementInitializer:
NonVoidInitializer
AssignExpression : NonVoidInitializer

An ArrayInitializer is a list of array element values enclosed in [ ]. The values can be optionally preceded by an index and a :. If an index is not supplied, it is set to the previous index plus 1, or 0 if it is the first value. Any missing elements will be initialized to the default value of the element type.

int[3] a = [ 1:2, 3 ]; // a[0] = 0, a[1] = 2, a[2] = 3

assert(a == [0, 2, 3]);

This is most handy when the array indices are given by enums:

enum Color { red, blue, green }

int[Color.max + 1] value =
[ Color.blue :6,
Color.green:2,
Color.red  :5 ];

assert(value == [5, 6, 2]);

Any indices must be known at compile-time. Note that if the array type is not specified and every element has an index, it will be inferred as an associative array literal.

int n = 4;
auto aa = [0:1, 3:n]; // associative array `int[int]`

int[] a = [1, 3:n, 5];
assert(a == [1, 0, 0, n, 5]);

//int[] e = [n:2]; // error, n not known at compile-time

Static Initialization of Statically Allocated Arrays

All elements of a static array can be initialized to a specific value with:

int[4] a = 42; // set all elements of a to 42

assert(a == [42, 42, 42, 42]);

These arrays are statically allocated when they appear in global scope. Otherwise, they need to be marked with const or static storage classes to make them statically allocated arrays.

String Literal Initializers

A static array of character type is default-initialized the same as other static arrays. However, when initialized from a string literal:

- The literal can be of a shorter length than the array.

- Any remaining elements are initialized to \0.

char[10] s1;
assert(s1[9] == '\xFF'); // char.init

char[10] s2 = "hi";
assert(s2[9] == '\0');

Rationale: A mutable character array's .ptr property can be used to obtain a C string (if it has an element which is zero). When that array is initialized with a shorter string literal, it's likely the subsequent code will need zero-termination.

Special Array Types

Strings

A string is an array of immutable (read-only) characters. String literals essentially are an easy way to write character array literals.

char[] arr;
//arr = "abc";          // error, cannot implicitly convert expression `"abc"` of type `string` to `char[]`
arr = "abc".dup;        // ok, allocates mutable copy

string str1 = "abc";    // ok, same types
//str1 = arr;           // error, cannot implicitly convert expression `arr` of type `char[]` to `string`
str1 = arr.idup;        // ok, allocates an immutable copy of elements
assert(str1 == "abc");

string str2 = str1;     // ok, mutable slice of same immutable array contents

The name string is aliased to immutable(char)[]. The type immutable(char)[] represents an array of immutable chars. However, the reference to the string is mutable.

immutable(char)[] s = "foo";
s[0] = 'a';  // error, s[0] is immutable
s = "bar";   // ok, s itself is not immutable

If the reference to the string needs to be immutable as well, it can be declared immutable char[] or immutable string:

immutable char[] s = "foo";
s[0] = 'a';  // error, s refers to immutable data
s = "bar";   // error, s is immutable

Strings can be copied, compared, concatenated, and appended:

string s1;
immutable s2 = "ello";
s1 = s2;
s1 = "h" ~ s1;
if (s1 > "farro")
s1 ~= " there";

assert(s1 == "hello there");

with array semantics. Any generated temporaries get cleaned up by the garbage collector (or by using alloca()). Not only that, this works with any array not just a special String array.

String Literal Types

The type of a string literal is determined by the semantic phase of compilation. The type is determined by implicit conversion rules. If there are two equally applicable implicit conversions, the result is an error. To disambiguate these cases, a cast or a postfix of c, w or d can be used:

cast(immutable(wchar)[]) "abc" // this is an array of wchar characters
"abc"w                         // so is this

String literals that do not have a postfix character and that have not been cast can be implicitly converted between string, wstring, and dstring (see below) as necessary.

void fun()
{
char c;
wchar w;
dchar d;

c = 'b';     // c is assigned the character 'b'
w = 'b';     // w is assigned the wchar character 'b'
//w = 'bc';  // error - only one wchar character at a time
w = "b"[0];  // w is assigned the wchar character 'b'
w = "\r"[0]; // w is assigned the carriage return wchar character
d = 'd';     // d is assigned the character 'd'
}

Strings and Unicode

String data is encoded as follows:

Alias

Type

Encoding

string

immutable(char)[]

UTF-8

wstring

immutable(wchar)[]

UTF-16

dstring

immutable(dchar)[]

UTF-32

Note that built-in comparison operators operate on a code unit basis. The end result for valid strings is the same as that of code point for code point comparison as long as both strings are in the same normalization form. Since normalization is a costly operation not suitable for language primitives it's assumed to be enforced by the user.

The standard library lends a hand for comparing strings with mixed encodings (by transparently decoding, see std.algorithm.cmp), case-insensitive comparison and normalization.

Last but not least, a desired string sorting order differs by culture and language and is usually nothing like code point for code point comparison. The natural order of strings is obtained by applying the Unicode collation algorithm that should be implemented in the standard library.

Character Pointers and C strings

A pointer to a character can be generated:

string str = "abcd";
immutable(char)* p = &str[3]; // pointer to 4th element
assert(*p == 'd');
p = str.ptr; // pointer to 1st element
assert(*p == 'a');

Only string literals are zero-terminated in D. In general, when transferring a pointer to string data to C, append a terminating '\0':

string str = "ab";
assert(str.ptr[2] == '\0'); // OK
str ~= "cd";
// str is no longer zero-terminated
str ~= "\0";
assert(str[4] == '\0'); // OK
str.length = 2;
// str is no longer correctly zero-terminated
assert(str.ptr[2] != '\0');

The function std.string.toStringz can also be used.
Example: printf

core.stdc.stdio.printf is a C function and is not part of D. printf() will print C strings, which are 0 terminated. There are two ways to use printf() with D strings. The first is to add a terminating 0:

str ~= "\0";
printf("the string is '%s'\n", str.ptr);

or:

import std.string;
printf("the string is '%s'\n", std.string.toStringz(str));

String literals already have a 0 appended to them, so can be used directly:

printf("the string is '%s'\n", "string literal".ptr);

So, why does the first string literal to printf not need the .ptr? The first parameter is prototyped as a const(char)*, and a string literal can be implicitly converted to a const(char)*. The rest of the arguments to printf, however, are variadic (specified by ...), and a string literal typed immutable(char)[] cannot be passed to variadic parameters.

The second way is to use the precision specifier. The length comes first, followed by the pointer:

printf("the string is '%.*s'\n", cast(int)str.length, str.ptr);

The best way is to use std.stdio.writefln, which can handle D strings:

import std.stdio;
writefln("the string is '%s'", str);

Void Arrays

There are special types of array with a void element type which can hold arrays of any kind. Void arrays are used for low-level operations where some kind of array data is being handled, but the exact type of the array elements are unimportant. The .length of a void array is the length of the data in bytes, rather than the number of elements in its original type. Array indices in slicing operations are interpreted as byte indices. A void array cannot be indexed.

Another array can be copied into a void array's elements, but not in a @safe function. This is because the void array may hold pointers. A void array cannot be filled.

Arrays of any type can be implicitly converted to a (tail qualified) void array - the compiler inserts the appropriate calculations so that the .length of the resulting array's size is in bytes rather than number of elements. Void arrays cannot be converted back to the original type without using an array cast, and it is an error to convert to an array type whose element size does not evenly divide the length of the void array.

void main()
{
int[] data1 = [1,2,3];

void[] arr = data1;            // OK, int[] implicit converts to void[].
assert(data1.length == 3);
assert(arr.length == 12);      // length is implicitly converted to bytes.

arr[0..4] = [5];               // Assign first 4 bytes to 1 int element
assert(data1 == [5,2,3]);

arr ~= [6];                    // Append the 4 bytes of an int
//data1 = arr;                 // Error: void[] does not implicitly
// convert to int[].
int[] data2 = cast(int[]) arr; // OK, can convert with explicit cast.
assert(data2 is arr);          // both point to the same set of bytes
assert(data2 == [5,2,3,6]);
}

void main()
{
void[] arr = new void[12];
long[] bad = cast(long[]) arr; // Runtime error: long.sizeof == 8, which
// does not divide arr.length, which is 12
// bytes.
}

Void arrays can be static arrays if their length is known at compile-time. The length is specified in bytes:

void main()
{
byte[2] x;
int[2] y;

void[2] a = x; // OK, lengths match
void[2] b = y; // Error: int[2] is 8 bytes long, doesn't fit in 2 bytes.
}

While it may seem that void arrays are just fancy syntax for ubyte[], there is a subtle distinction. The garbage collector generally will not scan ubyte[] arrays for pointers, ubyte[] being presumed to contain only pure byte data, not pointers. However, it will scan void[] arrays for pointers, since such an array may have been implicitly converted from an array of pointers or an array of elements that contain pointers. Allocating an array that contains pointers as ubyte[] may run the risk of the GC collecting live memory if these pointers are the only remaining references to their targets.

Implicit Conversions

A pointer T* can be implicitly converted to one of the following:

- void*

A static array T[dim] can be implicitly converted to one of the following (U is a base class of T):

- T[]

- const(U)[]

- const(U[])

- void[]

A dynamic array T[] can be implicitly converted to one of the following (U is a base class of T):

- const(U)[]

- const(U[])

- void[]

Array literals can also be implicitly converted to static array types. See Array Literals for details.

String literals can also be implicitly converted to static array types and character pointer types. See String Literals for details.

Associative Arrays

Associative arrays have an index that is not necessarily an integer, and can be sparsely populated. The index for an associative array is called the key, and its type is called the KeyType.

Associative arrays are declared by placing the KeyType within the [ ] of an array declaration:

int[string] aa;   // Associative array of ints that are
// indexed by string keys.
// The KeyType is string.
aa["hello"] = 3;  // set value associated with key "hello" to 3
int value = aa["hello"];  // lookup value from a key
assert(value == 3);

Neither the KeyTypes nor the element types of an associative array can be function types or void.

Implementation Defined: The built-in associative arrays do not preserve the order of the keys inserted into the array. In particular, in a foreach loop the order in which the elements are iterated is typically unspecified.

Literals

auto aa = [21u: "he", 38: "ho", 2: "hi"];
static assert(is(typeof(aa) == string[uint]));
assert(aa[2] == "hi");

See Associative Array Literals.

Removing Keys

Particular keys in an associative array can be removed with the remove function:

aa.remove("hello");

remove(key) does nothing if the given key does not exist and returns false. If the given key does exist, it removes it from the AA and returns true.

All keys can be removed by using the method clear.

Testing Membership

The InExpression yields a pointer to the value if the key is in the associative array, or null if not:

int* p;

p = "hello" in aa;
if (p !is null)
{
*p = 4;  // update value associated with key
assert(aa["hello"] == 4);
}

Undefined Behavior: Adjusting the pointer to point before or after the element whose address is returned, and then dereferencing it.

Using Classes as the KeyType

Classes can be used as the KeyType. The behavior is controlled by the following member functions of class Object:

- size_t toHash() @trusted nothrow

- bool opEquals(Object)

Note that the parameter to opEquals is of type Object, not the type of the class in which it is defined.

For example:

class Foo
{
int a, b;

override size_t toHash() { return a + b; }

override bool opEquals(Object o)
{
Foo foo = cast(Foo) o;
return foo && a == foo.a && b == foo.b;
}
}

The default implementation of opEquals uses the address of the instance for comparisons, and the default implementation of toHash hashes the address of the instance.
Implementation Defined: opCmp is not used to check for equality by the associative array. However, since the actual opEquals or opCmp called is not decided until runtime, the compiler cannot always detect mismatched functions. Because of legacy issues, the compiler may reject an associative array key type that overrides opCmp but not opEquals. This restriction may be removed in future versions.

Undefined Behavior:

1. If toHash must consistently be the same value when opEquals returns true. In other words, two objects that are considered equal should always have the same hash value. Otherwise, undefined behavior will result.

Best Practices:

1. Use the attributes @safe, @nogc, pure, const, and scope as much as possible on the toHash and opEquals overrides.

Using Structs or Unions as the KeyType

If the KeyType is a struct or union type, a default mechanism is used to compute the hash and comparisons of it based on the fields of the struct value. A custom mechanism can be used by providing the following functions as struct members:

size_t toHash() const @safe pure nothrow;
bool opEquals(ref const typeof(this) s) const @safe pure nothrow;

For example:

import std.string;

struct MyString
{
string str;

size_t toHash() const @safe pure nothrow
{
size_t hash;
foreach (char c; str)
hash = (hash * 9) + c;
return hash;
}

bool opEquals(ref const MyString s) const @safe pure nothrow
{
return std.string.cmp(this.str, s.str) == 0;
}
}

The functions can use @trusted instead of @safe.

Implementation Defined: opCmp is not used to check for equality by the associative array. For this reason, and for legacy reasons, an associative array key is not allowed to define a specialized opCmp, but omit a specialized opEquals. This restriction may be removed in future versions of D.

Undefined Behavior:

1. If toHash must consistently be the same value when opEquals returns true. In other words, two structs that are considered equal should always have the same hash value. Otherwise, undefined behavior will result.

Best Practices:

1. Use the attributes @nogc as much as possible on the toHash and opEquals overrides.

Construction or Assignment on Setting AA Entries

When an AA indexing access appears on the left side of an assignment operator, it is specially handled for setting an AA entry associated with the key.

string[int] aa;
string s;

//s = aa[1];        // throws RangeError in runtime
aa[1] = "hello";    // handled for setting AA entry
s = aa[1];          // succeeds to lookup
assert(s == "hello");

If the assigned value type is equivalent with the AA element type:

1. If the indexing key does not yet exist in AA, a new AA entry will be allocated, and it will be initialized with the assigned value.

2. If the indexing key already exists in the AA, the setting runs normal assignment.

struct S
{
int val;
void opAssign(S rhs) { this.val = rhs.val * 2; }
}
S[int] aa;
aa[1] = S(10);  // first setting initializes the entry aa[1]
assert(aa[1].val == 10);
aa[1] = S(10);  // second setting invokes normal assignment, and
// operator-overloading rewrites it to member opAssign function.
assert(aa[1].val == 20);

If the assigned value type is not equivalent with the AA element type, the expression could invoke operator overloading with normal indexing access:

struct S
{
int val;
void opAssign(int v) { this.val = v * 2; }
}
S[int] aa;
aa[1] = 10;     // is rewritten to: aa[1].opAssign(10), and
// throws RangeError before opAssign is called

However, if the AA element type is a struct which supports an implicit constructor call from the assigned value, implicit construction is used for setting the AA entry:

struct S
{
int val;
this(int v) { this.val = v; }
void opAssign(int v) { this.val = v * 2; }
}
S s = 1;    // OK, rewritten to: S s = S(1);
s = 1;      // OK, rewritten to: s.opAssign(1);

S[int] aa;
aa[1] = 10; // first setting is rewritten to: aa[1] = S(10);
assert(aa[1].val == 10);
aa[1] = 10; // second setting is rewritten to: aa[1].opAssign(10);
assert(aa[1].val == 20);

This is designed for efficient memory reuse with some value-semantics structs, eg. std.bigint.BigInt.

import std.bigint;
BigInt[string] aa;
aa["a"] = 10;   // construct BigInt(10) and move it in AA
aa["a"] = 20;   // call aa["a"].opAssign(20)

Inserting if not present

When AA access requires that there must be a value corresponding to the key, a value must be constructed and inserted if not present. The require function provides a means to construct a new value via a lazy argument. The lazy argument is evaluated when the key is not present. The require operation avoids the need to perform multiple key lookups.

class C{}
C[string] aa;

auto a = aa.require("a", new C);   // lookup "a", construct if not present

Sometimes it is necessary to know whether the value was constructed or already exists. The require function doesn't provide a boolean parameter to indicate whether the value was constructed but instead allows the construction via a function or delegate. This allows the use of any mechanism as demonstrated below.

class C{}
C[string] aa;

bool constructed;
auto a = aa.require("a", { constructed=true; return new C;}());
assert(constructed == true);

C newc;
auto b = aa.require("b", { newc = new C; return newc;}());
assert(b is newc);

Advanced updating

Typically updating a value in an associative array is simply done with an assign statement.

int[string] aa;

aa["a"] = 3;  // set value associated with key "a" to 3

Sometimes it is necessary to perform different operations depending on whether a value already exists or needs to be constructed. The update function provides a means to construct a new value via the create delegate or update an existing value via the update delegate. The update operation avoids the need to perform multiple key lookups.

int[string] aa;

// create
aa.update("key",
() => 1,
(int) {} // not executed
);
assert(aa["key"] == 1);

// update value by ref
aa.update("key",
() => 0, // not executed
(ref int v) {
v += 1;
});
assert(aa["key"] == 2);

For details, see update.

Runtime Initialization of Immutable AAs

Immutable associative arrays are often desirable, but sometimes initialization must be done at runtime. This can be achieved with a constructor (static constructor depending on scope), a buffer associative array and assumeUnique:

immutable long[string] aa;

shared static this()
{
import std.exception : assumeUnique;
import std.conv : to;

long[string] temp; // mutable buffer
foreach (i; 0 .. 10)
{
temp[to!string(i)] = i;
}
temp.rehash; // for faster lookups

aa = assumeUnique(temp);
}

void main()
{
assert(aa["1"] == 1);
assert(aa["5"] == 5);
assert(aa["9"] == 9);
}

Construction and Reference Semantics

An Associative Array defaults to null, and is constructed upon assigning the first key/value pair. However, once constructed, an associative array has reference semantics, meaning that assigning one array to another does not copy the data. This is especially important when attempting to create multiple references to the same array.

int[int] aa;             // defaults to null
int[int] aa2 = aa;       // copies the null reference

assert(aa is null);
aa[1] = 1;
assert(aa2.length == 0); // aa2 still is null
aa2 = aa;
aa2[2] = 2;
assert(aa[2] == 2);      // now both refer to the same instance

A NewExpression allows constructing an associative array instance immediately, rather than when inserting a key.

int[string] a = new int[string];
auto b = a; // a and b point to the same AA instance

assert(b !is null);
a["1"] = 1;
assert(b["1"] == 1);

Properties

Properties for associative arrays are:

Associative Array Properties

Property

Description

.sizeof

Returns the size of the reference to the associative array; it is 4 in 32-bit builds and 8 on 64-bit builds.

.length

Returns number of values in the associative array. Unlike for dynamic arrays, it is read-only.

.dup

Create a new associative array of the same size and copy the contents of the associative array into it.

.keys

Returns dynamic array, the elements of which are the keys in the associative array.

.values

Returns dynamic array, the elements of which are the values in the associative array.

.rehash

Reorganizes the associative array in place so that lookups are more efficient. rehash is effective when, for example, the program is done loading up a symbol table and now needs fast lookups in it. Returns a reference to the reorganized array.

.clear

Removes all remaining keys and values from an associative array. The array is not rehashed after removal, to allow for the existing storage to be reused. This will affect all references to the same instance and is not equivalent to destroy(aa) which only sets the current reference to null

.byKey()

Returns a forward range suitable for use as a ForeachAggregate to a ForeachStatement which will iterate over the keys of the associative array.

.byValue()

Returns a forward range suitable for use as a ForeachAggregate to a ForeachStatement which will iterate over the values of the associative array.

.byKeyValue()

Returns a forward range suitable for use as a ForeachAggregate to a ForeachStatement which will iterate over key-value pairs of the associative array. The returned pairs are represented by an opaque type with .key and .value properties for accessing the key and value of the pair, respectively. Note that this is a low-level interface to iterating over the associative array and is not compatible with the Tuple type in Phobos. For compatibility with Tuple, use std.array.byPair instead.

.get(Key key, lazy Value defVal)

Looks up key; if it exists returns corresponding value else evaluates and returns defVal.

.require(Key key, lazy Value value)

Looks up key; if it exists returns corresponding value else evaluates value, adds it to the associative array and returns it.

.update(Key key, Value delegate() create, Value delegate(Value) update)

Looks up key; if it exists applies the update delegate else evaluates the create delegate and adds it to the associative array

Examples

Associative Array Example: word count

import std.algorithm;
import std.stdio;

void main()
{
ulong[string] dictionary;
ulong wordCount, lineCount, charCount;

foreach (line; stdin.byLine(KeepTerminator.yes))
{
charCount += line.length;
foreach (word; splitter(line))
{
wordCount += 1;
if (auto count = word in dictionary)
*count += 1;
else
dictionary[word.idup] = 1;
}

lineCount += 1;
}

writeln("   lines   words   bytes");
writefln("%8s%8s%8s", lineCount, wordCount, charCount);

const char[37] hr = '-';

writeln(hr);
foreach (word; sort(dictionary.keys))
{
writefln("%3s %s", dictionary[word], word);
}
}

See wc for the full version.

Associative Array Example: counting pairs

An Associative Array can be iterated in key/value fashion using a foreach statement. As an example, the number of occurrences of all possible substrings of length 2 (aka 2-mers) in a string will be counted:

import std.range : slide;
import std.stdio : writefln;
import std.utf : byCodeUnit; // avoids UTF-8 auto-decoding

int[string] aa;

// The string `arr` has a limited alphabet: {A, C, G, T}
// Thus, for better performance, iteration can be done _without_ decoding
auto arr = "AGATAGA".byCodeUnit;

// iterate over all pairs in the string and count each pair
// ('A', 'G'), ('G', 'A'), ('A', 'T'), ...
foreach (window; arr.slide(2))
aa[window.source]++; // source unwraps the code unit range

// iterate over all key/value pairs of the Associative Array
foreach (key, value; aa)
{
writefln("key: %s, value: %d", key, value);
}

> rdmd count.d
key: AT, value: 1
key: GA, value: 2
key: TA, value: 1
key: AG, value: 2

Structs and Unions

Introduction

Whereas classes are reference types, structs and unions are value types. Structs are simple aggregations of data and their associated operations on that data.

StructDeclaration:
struct Identifier ;
struct Identifier AggregateBody
StructTemplateDeclaration
AnonStructDeclaration

AnonStructDeclaration:
struct AggregateBody

UnionDeclaration:
union Identifier ;
union Identifier AggregateBody
UnionTemplateDeclaration
AnonUnionDeclaration

AnonUnionDeclaration:
union AggregateBody

AggregateBody:
{ DeclDefsopt }

The following example declares a struct type with a single integer field:

struct S
{
int i;
}

void main()
{
S a;
a.i = 3;

S b = a; // copy a
a.i++;
assert(a.i == 4);
assert(b.i == 3);
}

For local variables, a struct/union instance is allocated on the stack by default. To allocate on the heap, use new, which gives a pointer.

A pointer to a struct or union is automatically dereferenced when using the . operator to access members.

S* p = new S;
assert(p.i == 0); // `p.i` is the same as `(*p).i`

Note: There is no -> operator as in C.

A struct can contain multiple fields which are stored sequentially. Conversely, multiple fields in a union use overlapping storage.

union U
{
ubyte i;
char c;
}

void main()
{
U u;
u.i = 3;
assert(u.c == '\x03');
u.c++;
assert(u.i == 4);
}

Members

Struct Members

A struct definition can contain:

- Fields

- Static fields

- Anonymous Structs and Unions

- Member Functions

- Static member functions

- Constructors

- Destructors

- Invariants

- Operator Overloading

- Alias This

- Other declarations (see DeclDef)

A struct is defined to not have an identity; that is, the implementation is free to make bit copies of the struct as convenient.

Best Practices:

1. Bit fields are supported with the bitfields template.

Union Members

A union definition can contain:

- Fields

- Static fields

- Anonymous Structs and Unions

- member functions

- static member functions

- Constructors

- Operator Overloading

- Alias This

- Other declarations (see DeclDef)

Recursive Structs and Unions

Structs and unions may not contain a non-static instance of themselves, however, they may contain a pointer to the same type.

struct S
{
S* ptr;     // OK
S[] slice;  // OK

S s;        // error
S[2] array; // error

static S global; // OK
}

Struct Layout

The non-static data members of a struct are called fields. Fields are laid out in lexical order. Fields are aligned according to the Align Attribute in effect. Unnamed padding is inserted between fields to align fields. There is no padding between the first field and the start of the object.

extern(D) structs with no fields of non-zero size (aka Empty Structs) have a size of one byte.

extern(C) struct C {}
struct D {}

static assert(C.sizeof == 0);
static assert(D.sizeof == 1);

Non-static function-nested D structs, which access the context of their enclosing scope, have an extra field.

Implementation Defined:

1. The default layout of the fields of a struct is an exact match with the associated C compiler.

2. g++ and clang++ differ in how empty structs are handled. Both return 1 from sizeof, however, clang++ does not push them onto the parameter stack while g++ does. This is a binary incompatibility between g++ and clang++. dmd follows clang++ behavior for OSX and FreeBSD, and g++ behavior for Linux and other Posix platforms.

3. clang and gcc both return 0 from sizeof for empty structs. Using extern "C++" in clang++ and g++ does not cause them to conform to the behavior of their respective C compilers.

Undefined Behavior:

1. The padding data can be accessed, but its contents are undefined.

2. Do not pass or return structs with no fields of non-zero size to extern (C) functions. According to C11 6.7.2.1p8 this is undefined behavior.

Best Practices:

1. When laying out a struct to match an externally defined layout, use align attributes to describe an exact match. Using a Static Assert to ensure the result is as expected.

2. Although the contents of the padding are often zero, do not rely on that.

3. Avoid using empty structs when interfacing with C and C++ code.

4. Avoid using empty structs as parameters or arguments to variadic functions.

Plain Old Data

A struct or union is Plain Old Data (POD) if it meets the following criteria:

1. it is static, or not nested

2. it has no postblits, copy constructors, destructors, or assignment operators

3. it has no fields that are themselves non-POD

Best Practices: Structs or unions that interface with C code should be POD.

Opaque Structs and Unions

Opaque struct and union declarations do not have an AggregateBody:

struct S;
union U;
struct V(T);
union W(T);

The members are completely hidden to the user, and so the only operations on those types are ones that do not require any knowledge of the contents of those types. For example:

struct S;
S.sizeof; // error, size is not known
S s;      // error, cannot initialize unknown contents
S* p;     // ok, knowledge of members is not necessary

Best Practices: They can be used to implement the PIMPL idiom.

Initialization

Default Initialization of Structs

Struct fields are by default initialized to whatever the Initializer for the field is, and if none is supplied, to the default initializer for the field's type.

struct S { int a = 4; int b; }
S x; // x.a is set to 4, x.b to 0

The default initializers are evaluated at compile time.

Static Initialization of Structs

StructInitializer:
{ StructMemberInitializersopt }

StructMemberInitializers:
StructMemberInitializer
StructMemberInitializer ,
StructMemberInitializer , StructMemberInitializers

StructMemberInitializer:
NonVoidInitializer
Identifier : NonVoidInitializer

If a StructInitializer is supplied, each StructMemberInitializer initializes a matching field:

- A StructMemberInitializer using the Identifier : NonVoidInitializer syntax may appear in any order. The identifier must match a field name.

- If the first StructMemberInitializer does not specify an Identifier, it refers to the first field in the StructDeclaration.

- A subsequent NonVoidInitializer without an Identifier refers to the next field (in lexical order) after the one referred to in the previous StructMemberInitializer.

Any field not covered by a StructMemberInitializer is default initialized.

struct S { int a, b, c, d = 7; }

S r;                          // r.a = 0, r.b = 0, r.c = 0, r.d = 7
S s = { a:1, b:2 };           // s.a = 1, s.b = 2, s.c = 0, s.d = 7
S t = { c:4, b:5, a:2, d:5 }; // t.a = 2, t.b = 5, t.c = 4, t.d = 5
S u = { 1, 2 };               // u.a = 1, u.b = 2, u.c = 0, u.d = 7
S v = { 1, d:3 };             // v.a = 1, v.b = 0, v.c = 0, v.d = 3
S w = { b:1, 3 };             // w.a = 0, w.b = 1, w.c = 3, w.d = 7

Initializing a field more than once is an error:

S x = { 1, a:2 };  // error: duplicate initializer for field `a`

Default Initialization of Unions

Unions are by default initialized to whatever the Initializer for the first field is, and if none is supplied, to the default initializer for the first field's type. If the union is larger than the first field, the remaining bits are set to 0.

union U { int a = 4; long b; }
U x; // x.a is set to 4, x.b to an implementation-defined value

It is an error to supply initializers for members other than the first one.

union V { int a; long b = 4; }     // error: union field `b` with default initialization `4` must be before field `a`
union W { int a = 4; long b = 5; } // error: overlapping default initialization for `a` and `b`

The default initializer is evaluated at compile time.

Implementation Defined: The values the fields other than the default initialized field are set to.

Static Initialization of Unions

Unions are initialized similarly to structs, except that only one member initializer is allowed. If the member initializer does not specify an identifier, it will initialize the first member of the union.

union U { int a; double b; }
U u = { 2 };       // u.a = 2
U v = { b : 5.0 }; // v.b = 5.0

U w = { 2, 3 };    // error: overlapping initialization for field `a` and `b`

If the union is larger than the initialized field, the remaining bits are set to 0.

Implementation Defined: The values the fields other than the initialized field are set to.

Dynamic Initialization of Structs

The static initializer syntax can also be used to initialize non-static variables. The initializer need not be evaluable at compile time.

struct S { int a, b, c, d = 7; }

void test(int i)
{
S q = { 1, b:i }; // q.a = 1, q.b = i, q.c = 0, q.d = 7
}

Structs can be dynamically initialized from another value of the same type:

struct S { int a; }
S t;      // default initialized
t.a = 3;
S s = t;  // s.a is set to 3

If the struct has a constructor, and the struct is initialized with a value that is of a different type, then the constructor is called:

struct S
{
int a;

this(int v)
{
this.a = v;
}
}

S s = 3; // sets s.a to 3 using S's constructor

If the struct does not have a constructor but opCall is overridden for the struct, and the struct is initialized with a value that is of a different type, then the opCall operator is called:

struct S
{
int a;

static S opCall(int v)
{
S s;
s.a = v;
return s;
}

static S opCall(S v)
{
assert(0);
}
}

S s = 3; // sets s.a to 3 using S.opCall(int)
S t = s; // sets t.a to 3, S.opCall(S) is not called

Dynamic Initialization of Unions

The static initializer syntax can also be used to initialize non-static variables. The initializer need not be evaluable at compile time.

union U { int a; double b; }

void test(int i)
{
U u = { a : i };   // u.a = i
U v = { b : 5.0 }; // v.b = 5.0
}

Struct Literals

A struct literal consists of the name of the struct followed by a parenthesized named argument list:

struct S { int x; float y; }

S s1 = S(1, 2); // set field x to 1, field y to 2
S s2 = S(y: 2, x: 1); // same as above
assert(s1 == s2);

If a struct has a constructor or a member function named opCall, then struct literals for that struct are not possible. See also opCall operator overloading for the issue workaround.

Struct literals are syntactically like function calls.

Arguments are assigned to fields as follows:

1. If the first argument has no name, it will be assigned to the struct field that is defined first lexically.

2. A named argument is assigned to the struct field with the same name. It is an error if no such field exists.

3. Any other argument is assigned to the next lexically defined struct field relative to the preceding argument's struct field. It is an error if no such field exists, i.e. when the preceding argument assigns to the last struct field.

4. It is also an error to assign a field more than once.

5. Any fields not assigned a value are initialized with their respective default initializers.

Note: These rules are consistent with function calls, see Matching Arguments to Parameters.

If there is a union field in the struct, only one member of the union can be initialized inside a struct literal. This matches the behaviour for union literals.

struct S { int x = 1, y = 2, z = 3; }

S s0 = S(y: 5, 6, x: 4); // `6` is assigned to field `z`, which comes after `y`
assert(s0.z == 6);

S s1 = S(y: 5, z: 6);    // Field x is not assigned, set to default initializer `1`
assert(s1.x == 1);

//S s2 = S(y: 5, x: 4, 5); // Error: field `y` is assigned twice
//S s3 = S(z: 2, 3);       // Error: no field beyond `z`

Union Literals

A union literal is like a struct literal, but only one field can be initialized with an initializer expression. The remainder of the union's memory is initialized to zero.

union U
{
byte a;
char[2] b;
}

U u = U(2);
assert(u.a == 2);
assert(u.b == [2, 0]);

Anonymous Structs and Unions

An anonymous struct or union can be declared as a member of a parent class, struct or union by omitting the identifier after struct or union. An anonymous struct declares sequentially stored fields in the parent type. An anonymous union declares overlapping fields in the parent type.

An anonymous union is useful inside a class or struct to share memory for fields, without having to name a parent field with a separate union type.

struct S
{
int a;
union
{
byte b;
char c;
}
}

S s = S(1, 2);
assert(s.a == 1);
assert(s.b == 2);
assert(s.c == 2); // overlaps with `b`

Conversely, an anonymous struct is useful inside a union to declare multiple fields that are stored sequentially.

union U
{
int a;
struct
{
uint b;
bool c;
}
}

U u = U(1);
assert(u.a == 1);
assert(u.b == 1); // overlaps with `a`
assert(u.c == false); // no overlap

Struct Properties

Name

Description

.alignof

Size boundary struct needs to be aligned on

.tupleof

A symbol sequence of all struct fields - see class .tupleof for more details.

Struct Field Properties

Struct Field Properties

Name

Description

.offsetof

Offset in bytes of field from beginning of struct. See the align attribute for an example.

Const, Immutable and Shared Structs

A struct declaration can have a storage class of const, immutable or shared. It has an equivalent effect as declaring each member of the struct as const, immutable or shared.

const struct S { int a; int b = 2; }
void main()
{
S s = S(3); // initializes s.a to 3
S t;        // initializes t.a to 0
t = s;      // error, t.a and t.b are const, so cannot modify them.
t.a = 4;    // error, t.a is const
}

Union Constructors

Unions are constructed in the same way as structs.

Struct Constructors

Struct constructors are used to initialize an instance of a struct when a more complex construction is needed than is allowed by static initialization or a struct literal.

Constructors are defined with a function name of this and have no return value. The grammar is the same as for the class Constructor.

A struct constructor is called by the name of the struct followed by Parameters.

If the ParameterList is empty, the struct instance is default initialized.

struct S
{
int x, y = 4, z = 6;
this(int a, int b)
{
x = a;
y = b;
}
}

void main()
{
S a = S(4, 5); // calls S.this(4, 5):  a.x = 4, a.y = 5, a.z = 6
S b = S();  // default initialized:    b.x = 0, b.y = 4, b.z = 6
S c = S(1); // error, matching this(int) not found
}

Named arguments will be forwarded to the constructor and match parameter names, not struct field names.

struct S
{
int x;
int y;
this(int y, int z) { this.x = y; this.y = z; }
}
S a = S(x: 3, y: 4); // Error: constructor has no parameter named `x`
S b = S(y: 3, 4); // `y: 3` will set field `x` through parameter `y`

A default constructor (i.e. one with an empty ParameterList) is not allowed.

struct S
{
int x;
this() { } // error, struct default constructor not allowed
}

Delegating Constructors

A constructor can call another constructor for the same struct in order to share common initializations. This is called a delegating constructor:

struct S
{
int j = 1;
long k = 2;
this(long k)
{
this.k = k;
}
this(int i)
{
// At this point: j=1, k=2
this(6); // delegating constructor call
// At this point: j=1, k=6
j = i;
// At this point: j=i, k=6
}
}

The following restrictions apply:

1. If a constructor's code contains a delegating constructor call, all possible execution paths through the constructor must make exactly one delegating constructor call:

struct S
{
int a;
this(int i) { }

this(char c)
{
c || this(1);  // error, not on all paths
}

this(wchar w)
{
(w) ? this(1) : this('c');  // ok
}

this(byte b)
{
foreach (i; 0 .. b)
{
this(1);  // error, inside loop
}
}
}

2. It is illegal to refer to this implicitly or explicitly prior to making a delegating constructor call.

3. Once the delegating constructor returns, all fields are considered constructed.

4. Delegating constructor calls cannot appear after labels.

See also: delegating class constructors.

Struct Instantiation

When an instance of a struct is created, the following steps happen:

1. The raw data is statically initialized using the values provided in the struct definition. This operation is equivalent to doing a memory copy of a static version of the object onto the newly allocated one.

2. If there is a constructor defined for the struct, the constructor matching the argument list is called.

3. If struct invariant checking is turned on, the struct invariant is called at the end of the constructor.

Constructor Attributes

A constructor qualifier (const, immutable or shared) constructs the object instance with that specific qualifier.

struct S1
{
int[] a;
this(int n) { a = new int[](n); }
}
struct S2
{
int[] a;
this(int n) immutable { a = new int[](n); }
}
void main()
{
// Mutable constructor creates mutable object.
S1 m1 = S1(1);

// Constructed mutable object is implicitly convertible to const.
const S1 c1 = S1(1);

// Constructed mutable object is not implicitly convertible to immutable.
immutable i1 = S1(1); // error

// Mutable constructor cannot construct immutable object.
auto x1 = immutable S1(1); // error

// Immutable constructor creates immutable object.
immutable i2 = immutable S2(1);

// Immutable constructor cannot construct mutable object.
auto x2 = S2(1); // error

// Constructed immutable object is not implicitly convertible to mutable.
S2 m2 = immutable S2(1); // error

// Constructed immutable object is implicitly convertible to const.
const S2 c2 = immutable S2(1);
}

Constructors can be overloaded with different attributes.

struct S
{
this(int);           // non-shared mutable constructor
this(int) shared;    // shared mutable constructor
this(int) immutable; // immutable constructor
}

void fun()
{
S m = S(1);
shared s = shared S(2);
immutable i = immutable S(3);
}

Pure Constructors

If the constructor can create a unique object (i.e. if it is pure), the object is implicitly convertible to any qualifiers.

struct S
{
this(int) pure;
// Based on the definition, this creates a mutable object. But the
// created object cannot contain any mutable global data.
// Therefore the created object is unique.

this(int[] arr) immutable pure;
// Based on the definition, this creates an immutable object. But
// the argument int[] never appears in the created object so it
// isn't implicitly convertible to immutable. Also, it cannot store
// any immutable global data.
// Therefore the created object is unique.
}

void fun()
{
immutable i = immutable S(1); // this(int) pure is called
shared s = shared S(1);       // this(int) pure is called
S m = S([1,2,3]);             // this(int[]) immutable pure is called
}

Disabling Default Struct Construction

If a struct constructor is annotated with @disable and has an empty ParameterList, the struct has disabled default construction. The only way it can be constructed is via a call to another constructor with a non-empty ParameterList.

A struct with a disabled default constructor, and no other constructors, cannot be instantiated other than via a VoidInitializer.

A disabled default constructor may not have a FunctionBody.

If any fields have disabled default construction, struct default construction is also disabled.

struct S
{
int x;

// Disables default construction
@disable this();

this(int v) { x = v; }
}
struct T
{
int y;
S s;
}
void main()
{
S s;          // error: default construction is disabled
S t = S();    // error: also disabled
S u = S(1);   // constructed by calling `S.this(1)`
S v = void;   // not initialized, but allowed
S w = { 1 };  // error: cannot use { } since constructor exists
S[3] a;       // error: default construction is disabled
S[3] b = [S(1), S(20), S(-2)]; // ok
T t;          // error: default construction is disabled
}

Best Practices: Disabling default construction is useful when the default value, such as null, is not acceptable.

Field initialization inside a constructor

In a constructor body, if a delegating constructor is called, all field assignments are considered assignments. Otherwise, the first instance of field assignment is its initialization, and assignments of the form field = expression are treated as equivalent to typeof(field)(expression). The values of fields may be read before initialization or construction with a delegating constructor.

struct S
{
int num;
int ber;
this(int i)
{
num = i + 1;   // initialization
num = i + 2;   // assignment
ber = ber + 1; // ok to read before initialization
}
this(int i, int j)
{
this(i);
num = i + 1;  // assignment
}
}

If the field type has an opAssign method, it will not be used for initialization.

struct A
{
this(int n) {}
void opAssign(A rhs) {}
}
struct S
{
A val;
this(int i)
{
val = A(i);  // val is initialized to the value of A(i)
val = A(2);  // rewritten to val.opAssign(A(2))
}
}

If the field type is not mutable, multiple initialization will be rejected.

struct S
{
immutable int num;
this(int)
{
num = 1;  // OK
num = 2;  // Error: assignment to immutable
}
}

If the field is initialized on one path, it must be initialized on all paths.

struct S
{
immutable int num;
immutable int ber;
this(int i)
{
if (i)
num = 3;   // initialization
else
num = 4;   // initialization
}
this(long j)
{
j ? (num = 3) : (num = 4); // ok
j || (ber = 3);  // Error: initialized on only one path
j && (ber = 3);  // Error: initialized on only one path
}
}

A field initialization may not appear in a loop or after a label.

struct S
{
immutable int num;
immutable string str;
this(int j)
{
foreach (i; 0..j)
{
num = 1;    // Error: field initialization not allowed in loops
}
size_t i = 0;
Label:
str = "hello";  // Error: field initialization not allowed after labels
if (i++ < 2)
goto Label;
}
this(int j, int k)
{
switch (j)
{
case 1: ++j; break;
default: break;
}
num = j;        // Error: `case` and `default` are also labels
}
}

If a field's type has disabled default construction, then it must be initialized in the constructor.

struct S { int y; @disable this(); }

struct T
{
S s;
this(S t) { s = t; }       // ok
this(int i) { this('c'); } // ok
this(char) { }             // Error: s not initialized
}

Struct Copy Constructors

Warning: The plan is for copy constructors to replace postblit constructors, leaving postblit constructors only in legacy code. However, because the compiler hooks in druntime which deal with dynamic arrays and associative arrays have not yet all been updated to support copy constructors properly (issue #20970), any type that might be used in a dynamic array or associative array should use a postblit constructor rather than a copy constructor. The copy constructor will not be called in all the cases where it should be for the elements of a dynamic array or the keys or values in an associative array. Postblit constructors do not have this problem.

For backward compatibility reasons, a struct that explicitly defines both a copy constructor and a postblit will only use the postblit for implicit copying. However, if the postblit is disabled, the copy constructor will be used. If a struct defines a copy constructor (user-defined or generated) and has fields that define postblits, a deprecation will be issued, informing that the postblit will have priority over the copy constructor.

Copy constructors are used to initialize a struct instance from another instance of the same type. A struct that defines a copy constructor is not POD.

A constructor declaration is a copy constructor declaration if it meets the following requirements:

- It takes exactly one parameter without a default argument, followed by any number of parameters with default arguments.

- Its first parameter is a ref parameter.

- The type of its first parameter is the same type as typeof(this), optionally with one or more type qualifiers applied to it.

- It is not a template constructor declaration.

struct A
{
this(ref return scope A rhs) {}                        // copy constructor
this(ref return scope const A rhs, int b = 7) {}       // copy constructor with default parameter
}

The copy constructor is type checked as a normal constructor.

If a copy constructor is defined, implicit calls to it will be inserted in the following situations:

1. When a variable is explicitly initialized:

struct A
{
int[] arr;
this(ref return scope A rhs) { arr = rhs.arr.dup; }
}

void main()
{
A a;
a.arr = [1, 2];

A b = a; // copy constructor gets called
b.arr[] += 1;
assert(a.arr == [1, 2]); // a is unchanged
assert(b.arr == [2, 3]);
}

2. When a parameter is passed by value to a function:

struct A
{
this(ref return scope A another) {}
}

void fun(A a) {}

void main()
{
A a;
fun(a);    // copy constructor gets called
}

3. When a parameter is returned by value from a function and Named Returned Value Optimization (NRVO) cannot be performed:

struct A
{
this(ref return scope A another) {}
}

A fun()
{
A a;
return a;       // NRVO, no copy constructor call
}

A a;
A gun()
{
return a;       // cannot perform NRVO, rewrite to: return (A __tmp; __tmp.copyCtor(a));
}

void main()
{
A a = fun();
A b = gun();
}

Disabled Copying

When a copy constructor is defined for a struct (or marked @disable), the compiler no longer implicitly generates default copy/blitting constructors for that struct:

struct A
{
int[] a;
this(ref return scope A rhs) {}
}

void fun(immutable A) {}

void main()
{
immutable A a;
fun(a);        // error: copy constructor cannot be called with types (immutable) immutable
}

struct A
{
@disable this(ref A);
}

void main()
{
A a;
A b = a; // error: copy constructor is disabled
}

If a union U has fields that define a copy constructor, whenever an object of type U is initialized by copy, an error will be issued. The same rule applies to overlapped fields (anonymous unions).

struct S
{
this(ref S);
}

union U
{
S s;
}

void main()
{
U a;
U b = a; // error, could not generate copy constructor for U
}

Copy Constructor Attributes

The copy constructor can be overloaded with different qualifiers applied to the parameter (copying from a qualified source) or to the copy constructor itself (copying to a qualified destination):

struct A
{
this(ref return scope A another) {}                        // 1 - mutable source, mutable destination
this(ref return scope immutable A another) {}              // 2 - immutable source, mutable destination
this(ref return scope A another) immutable {}              // 3 - mutable source, immutable destination
this(ref return scope immutable A another) immutable {}    // 4 - immutable source, immutable destination
}

void main()
{
A a;
immutable A ia;

A a2 = a;      // calls 1
A a3 = ia;     // calls 2
immutable A a4 = a;     // calls 3
immutable A a5 = ia;    // calls 4
}

The inout qualifier may be applied to the copy constructor parameter in order to specify that mutable, const, or immutable types are treated the same:

struct A
{
this(ref return scope inout A rhs) immutable {}
}

void main()
{
A r1;
const(A) r2;
immutable(A) r3;

// All call the same copy constructor because `inout` acts like a wildcard
immutable(A) a = r1;
immutable(A) b = r2;
immutable(A) c = r3;
}

Implicit Copy Constructors

A copy constructor is generated implicitly by the compiler for a struct S if all of the following conditions are met:

1. S does not explicitly declare any copy constructors;

2. S defines at least one direct member that has a copy constructor, and that member is not overlapped (by means of union) with any other member.

If the restrictions above are met, the following copy constructor is generated:

this(ref return scope inout(S) src) inout
{
foreach (i, ref inout field; src.tupleof)
this.tupleof[i] = field;
}

If the generated copy constructor fails to type check, it will receive the @disable attribute.

Struct Move Constructors

Move constructors are very much like copy constructors. The difference is that copy constructors make a copy of the original, while move constructors move the contents of the original, and the lifetime of the original ends.

Note: Do not use postblits in the same struct with move constructors.

If a move constructor is declared, also declare a copy constructor.

A constructor declaration is a move constructor declaration if it meets the following requirements:

- It takes exactly one parameter without a default argument, followed by any number of parameters with default arguments.

- Its first parameter is not a ref parameter.

- The type of its first parameter is the same type as typeof(this), optionally with one or more type qualifiers applied to it.

- It is not a template constructor declaration.

struct A
{
this(ref return scope A rhs) {}                    // copy constructor
this(return scope A rhs) {}                        // move constructor
this(return scope const A rhs, int b = 7) {}       // move constructor with default parameter
}

The move constructor is type checked as a normal constructor.

The move constructor's first parameter only accepts rvalues. An lvalue can be coerced into being an rvalue using __rvalue(Expression).

If a move constructor is defined, implicit calls to it will be inserted in the following situations:

1. When a variable is explicitly initialized:

struct A
{
int[] arr;
this(return scope A rhs) // move constructor
{
arr = rhs.arr;
rhs.arr = null; // do not leave dangling reference to array
}
this(ref return scope A rhs) { assert(0); }
}

void main()
{
A a;
a.arr = [1, 2];

A b = __rvalue(a); // move constructor gets called
b.arr[] += 1;
assert(a.arr is null); // a.arr is gone
assert(b.arr == [2, 3]);
}

2. When a parameter is passed by value to a function:

struct A
{
this(return scope A another) {} // move constructor
this(ref return scope A rhs) { assert(0); }
}

void fun(A a) {}

void main()
{
A a;
fun(__rvalue(a)); // move constructor gets called
// `a` is no longer a valid object
}

Disabled Moving

When a move constructor is defined for a struct (or marked @disable), the compiler no longer implicitly generates default move constructors for that struct:

struct A
{
this(ref A);
@disable this(A);
}

void main()
{
A a;
A b = __rvalue(a); // error: move constructor is disabled
}

If a union U has fields that define a move constructor, whenever an object of type U is initialized by move, an error will be issued. The same rule applies to overlapped fields (anonymous unions).

struct S
{
this(ref S);
this(S);
}

union U
{
S s;
}

void main()
{
U a;
U b = __rvalue(a); // error, could not generate move constructor for U
}

Move Constructor Attributes

The move constructor can be overloaded with different qualifiers applied to the parameter (moving from a qualified source) or to the move constructor itself (moving to a qualified destination):

struct A
{
this(ref return scope A another) { assert(0); }        // copy constructor
this(return scope A another) {}                        // 1 - mutable source, mutable destination
this(return scope immutable A another) {}              // 2 - immutable source, mutable destination
this(return scope A another) immutable {}              // 3 - mutable source, immutable destination
this(return scope immutable A another) immutable {}    // 4 - immutable source, immutable destination
}

void main()
{
A a;
immutable A ia;

A a2 = __rvalue(a);      // calls 1
A a3 = __rvalue(ia);     // calls 2

A b;
immutable A ib;

immutable A b4 = __rvalue(b);     // calls 3
immutable A b5 = __rvalue(ib);    // calls 4
}

The inout qualifier may be applied to the move constructor parameter in order to specify that mutable, const, or immutable types are treated the same:

struct A
{
this(ref return scope inout A rhs) immutable { assert(0); }
this(return scope inout A rhs) immutable {}
}

void main()
{
A r1;
const(A) r2;
immutable(A) r3;

// All call the same move constructor because `inout` acts like a wildcard
immutable(A) a = __rvalue(r1);
immutable(A) b = __rvalue(r2);
immutable(A) c = __rvalue(r3);
}

Implicit Move Constructors

A move constructor is generated implicitly by the compiler for a struct S if all of the following conditions are met:

1. S does not explicitly declare any move constructors;

2. S defines at least one direct member that has a move constructor, and that member is not overlapped (by means of union) with any other member.

If the restrictions above are met, the following move constructor is generated:

this(return scope inout(S) src) inout
{
foreach (i, ref inout field; src.tupleof)
this.tupleof[i] = __rvalue(field);
}

If the generated move constructor fails to type check, it will receive the @disable attribute.

import core.stdc.stdio;

struct T
{
int i;
inout this(ref inout T t) { this.i = t.i - 1; printf("this(ref T)\n"); }
inout this(inout T t)     { this.i = t.i + 1; printf("this(T)\n"); }
}

struct S
{
T t;
}

void main()
{
S s;
s.t.i = 3;
S u = s;
printf("u.t.i = %d\n", u.t.i);
assert(u.t.i == 2);

S v = __rvalue(u);
printf("v.t.i = %d\n", v.t.i);
assert(v.t.i == 3);
}

Struct Postblits

Postblit:
this ( this ) MemberFunctionAttributesopt FunctionBody
this ( this ) MemberFunctionAttributesopt MissingFunctionBody

Warning: The plan is for copy constructors to replace postblit constructors, leaving postblit constructors only in legacy code. However, because the compiler hooks in druntime which deal with dynamic arrays and associative arrays have not yet all been updated to support copy constructors properly (issue #20970), any type that might be used in a dynamic array or associative array should use a postblit constructor rather than a copy constructor. The copy constructor will not be called in all the cases where it should be for the elements of a dynamic array or the keys or values in an associative array. Postblit constructors do not have this problem.

For backward compatibility reasons, a struct that explicitly defines both a copy constructor and a postblit will only use the postblit for implicit copying. However, if the postblit is disabled, the copy constructor will be used. If a struct defines a copy constructor (user-defined or generated) and has fields that define postblits, a deprecation will be issued, informing that the postblit will have priority over the copy constructor.

Copy construction is defined as initializing a struct instance from another instance of the same type. Copy construction is divided into two parts:

1. blitting the fields, i.e. copying the bits

2. running postblit on the result

The first part is done automatically by the language, the second part is done if a postblit function is defined for the struct. The postblit has access only to the destination struct object, not the source. Its job is to ‘fix up’ the destination as necessary, such as making copies of referenced data, incrementing reference counts, etc. For example:

struct S
{
int[] a;    // array is privately owned by this instance
this(this)
{
a = a.dup;
}
}

Disabling struct postblit makes the object not copyable.

struct T
{
@disable this(this);  // disabling makes T not copyable
}
struct S
{
T t;   // uncopyable member makes S also not copyable
}

void main()
{
S s;
S t = s; // error, S is not copyable
}

Depending on the struct layout, the compiler may generate the following internal postblit functions:

1. void __postblit(). The compiler assigns this name to the explicitly defined postblit this(this) so that it can be treated exactly as a normal function. Note that if a struct defines a postblit, it cannot define a function named __postblit - no matter the signature - as this would result in a compilation error due to the name conflict.

2. void __fieldPostblit(). If a struct X has at least one struct member that in turn defines (explicitly or implicitly) a postblit, then a field postblit is generated for X that calls all the underlying postblits of the struct fields in declaration order.

3. void __aggrPostblit(). If a struct has an explicitly defined postblit and at least 1 struct member that has a postblit (explicit or implicit) an aggregated postblit is generated which calls __fieldPostblit first and then __postblit.

4. void __xpostblit(). The field and aggregated postblits, although generated for a struct, are not actual struct members. In order to be able to call them, the compiler internally creates an alias, called __xpostblit which is a member of the struct and which points to the generated postblit that is the most inclusive.

// struct with alias __xpostblit = __postblit
struct X
{
this(this) {}
}

// struct with alias __xpostblit = __fieldPostblit
// which contains a call to X.__xpostblit
struct Y
{
X a;
}

// struct with alias __xpostblit = __aggrPostblit which contains
// a call to Y.__xpostblit and a call to Z.__postblit
struct Z
{
Y a;
this(this) {}
}

void main()
{
// X has __postblit and __xpostblit (pointing to __postblit)
static assert(__traits(hasMember, X, "__postblit"));
static assert(__traits(hasMember, X, "__xpostblit"));

// Y does not have __postblit, but has __xpostblit (pointing to __fieldPostblit)
static assert(!__traits(hasMember, Y, "__postblit"));
static assert(__traits(hasMember, Y, "__xpostblit"));
// __fieldPostblit is not a member of the struct
static assert(!__traits(hasMember, Y, "__fieldPostblit"));

// Z has  __postblit and __xpostblit (pointing to __aggrPostblit)
static assert(__traits(hasMember, Z, "__postblit"));
static assert(__traits(hasMember, Z, "__xpostblit"));
// __aggrPostblit is not a member of the struct
static assert(!__traits(hasMember, Z, "__aggrPostblit"));
}

Neither of the above postblits is defined for structs that don't define this(this) and don't have fields that transitively define it. If a struct does not define a postblit (implicit or explicit) but defines functions that use the same name/signature as the internally generated postblits, the compiler is able to identify that the functions are not actual postblits and does not insert calls to them when the struct is copied. Example:

struct X
{}

int a;

struct Y
{
int a;
X b;
void __fieldPostPostblit()
{
a = 42;
}
}

void main()
{
static assert(!__traits(hasMember, X, "__postblit"));
static assert(!__traits(hasMember, X, "__xpostblit"));

static assert(!__traits(hasMember, Y, "__postblit"));
static assert(!__traits(hasMember, Y, "__xpostblit"));

Y y;
auto y2 = y;
assert(a == 0); // __fieldPostBlit does not get called
}

Postblits cannot be overloaded. If two or more postblits are defined, even if the signatures differ, the compiler assigns the __postblit name to both and later issues a conflicting function name error:

struct X
{
this(this) {}
this(this) const {} // error: function X.__postblit conflicts with function X.__postblit
}

The following describes the behavior of the qualified postblit definitions:

1. const. When a postblit is qualified with const as in this(this) const; or const this(this); then the postblit is successfully called on mutable (unqualified), const, and immutable objects, but the postblit cannot modify the object because it regards it as const; hence const postblits are of limited usefulness. Example:

struct S
{
int n;
this(this) const
{
import std.stdio : writeln;
writeln("postblit called");
//++n; // error: cannot modify this.n in `const` function
}
}

void main()
{
S s1;
auto s2 = s1;
const S s3;
auto s4 = s3;
immutable S s5;
auto s6 = s5;
}

2. immutable. When a postblit is qualified with immutable as in this(this) immutable or immutable this(this) the code is ill-formed. The immutable postblit passes the compilation phase but cannot be invoked. Example:

struct Y
{
// not invoked anywhere, no error is issued
this(this) immutable
{ }
}

struct S
{
this(this) immutable
{ }
}

void main()
{
S s1;
auto s2 = s1;    // error: immutable method `__postblit` is not callable using a mutable object
const S s3;
auto s4 = s3;    // error: immutable method `__postblit` is not callable using a mutable object
immutable S s5;
auto s6 = s5;    // error: immutable method `__postblit` is not callable using a mutable object
}

3. shared. When a postblit is qualified with shared as in this(this) shared or shared this(this) solely shared objects may invoke the postblit; attempts of postbliting unshared objects will result in compile time errors:

struct S
{
this(this) shared
{ }
}

void main()
{
S s1;
auto s2 = s1;    // error: shared method `__postblit` is not callable using a non-shared object
const S s3;
auto s4 = s3;    // error: shared method `__postblit` is not callable using a non-shared object
immutable S s5;
auto s6 = s5;    // error: shared method `__postblit` is not callable using a non-shared object

// calling the shared postblit on a shared object is accepted
shared S s7;
auto s8 = s7;
}

An unqualified postblit will get called even if the struct is instantiated as immutable or const, but the compiler issues an error if the struct is instantiated as shared:

struct S
{
int n;
this(this) { ++n; }
}

void main()
{
immutable S a;      // shared S a; => error : non-shared method is not callable using a shared object
auto a2 = a;
import std.stdio: writeln;
writeln(a2.n);     // prints 1
}

From a postblit perspective, qualifiying the struct definition yields the same result as explicitly qualifying the postblit.

The following table lists all the possibilities of grouping qualifiers for a postblit associated with the type of object that needs to be used in order to successfully invoke the postblit:

Qualifier Groups

object type to be invoked on

const

immutable

shared

any object type

✔

uncallable

✔

shared object

✔

uncallable

✔

✔

shared object

✔

✔

uncallable

✔

✔

uncallable

✔

✔

✔

Note that when const and immutable are used to explicitly qualify a postblit as in this(this) const immutable; or const immutable this(this); - the order in which the qualifiers are declared does not matter - the compiler generates a conflicting attribute error, however declaring the struct as const/immutable and the postblit as immutable/const achieves the effect of applying both qualifiers to the postblit. In both cases the postblit is qualified with the more restrictive qualifier, which is immutable.

The postblits __fieldPostblit and __aggrPostblit are generated without any implicit qualifiers and are not considered struct members. This leads to the situation where qualifying an entire struct declaration with const or immutable does not have any impact on the above-mentioned postblits. However, since __xpostblit is a member of the struct and an alias of one of the other postblits, the qualifiers applied to the struct will affect the aliased postblit.

struct S
{
this(this)
{ }
}

// `__xpostblit` aliases the aggregated postblit so the `const` applies to it.
// However, the aggregated postblit calls the field postblit which does not have
// any qualifier applied, resulting in a qualifier mismatch error
const struct B
{
S a;        // error : mutable method B.__fieldPostblit is not callable using a const object
this(this)
{ }
}

// `__xpostblit` aliases the field postblit; no error
const struct B2
{
S a;
}

// Similar to B
immutable struct C
{
S a;        // error : mutable method C.__fieldPostblit is not callable using a immutable object
this(this)
{ }
}

// Similar to B2, compiles
immutable struct C2
{
S a;
}

In the above situations the errors do not contain line numbers because the errors are regarding generated code.

Qualifying an entire struct as shared correctly propagates the attribute to the generated postblits:

shared struct A
{
this(this)
{
import std.stdio : writeln;
writeln("the shared postblit was called");
}
}

struct B
{
A a;
}

void main()
{
shared B b1;
auto b2 = b1;
}

Unions may have fields that have postblits. However, a union itself never has a postblit. Copying a union does not result in postblit calls for any fields. If those calls are desired, they must be inserted explicitly by the programmer:

struct S
{
int count;
this(this)
{
++count;
}
}

union U
{
S s;
}

void main()
{
U a = U.init;
U b = a;
assert(b.s.count == 0);
b.s.__postblit;
assert(b.s.count == 1);
}

Member Functions (a.k.a. Methods)

A struct/union can have non-static member functions, like classes. Such functions (called instance methods) have a hidden this parameter which is a reference to the struct instance. However, an instance method can still be called on an rvalue struct instance, even if the method is not const:

struct S
{
int i;
int f() => ++i;
}

void main()
{
//S().i++; // cannot modify, `S().i` is not an lvalue
assert(S().f() == 1); // OK
}

Rationale: An instance method may have other side effects besides mutating a field, or it may produce a useful return value. In the general case, throwing away changes to a field after the method returns does not necessarily indicate a logic error.

Struct Destructors

Destructors are called implicitly when an object goes out of scope, or before an assignment (by default). Their purpose is to free up resources owned by the struct object.

struct S
{
int i;

~this()
{
import std.stdio;
writeln("S(", i, ") is being destructed");
}
}

void main()
{
auto s1 = S(1);
{
auto s2 = S(2);
// s2 destructor called
}
S(3); // s3 destructor called
// s1 destructor called
}

If the struct has a field of another struct type which itself has a destructor, that destructor will be called at the end of the parent destructor. If there is no parent destructor, the compiler will generate one. Similarly, a static array of a struct type with a destructor will have the destructor called for each element when the array goes out of scope.

struct S
{
char c;

~this()
{
import std.stdio;
writeln("S(", c, ") is being destructed");
}
}

struct Q
{
S a;
S b;
}

void main()
{
Q q = Q(S('a'), S('b'));
S[2] arr = [S('0'), S('1')];
// destructor called for arr[1], arr[0], q.b, q.a
}

A destructor for a struct instance can also be called early using destroy. Note that the destructor will still be called again when the instance goes out of scope.

Struct destructors are used for RAII.

Union Field Destruction

Unions may have fields that have destructors. However, a union itself never has a destructor. When a union goes out of scope, destructors for its fields are not called. If those calls are desired, they must be inserted explicitly by the programmer:

struct S
{
~this()
{
import std.stdio;
writeln("S is being destructed");
}
}

union U
{
S s;
}

void main()
{
import std.stdio;
{
writeln("entering first scope");
U u = U.init;
scope (exit) writeln("exiting first scope");
}
{
writeln("entering second scope");
U u = U.init;
scope (exit)
{
writeln("exiting second scope");
destroy(u.s);
}
}
}

Struct Invariants

Invariant:
invariant ( ) BlockStatement
invariant BlockStatement
invariant ( AssertArguments ) ;

Struct Invariants specify the relationships among the members of a struct instance. Those relationships must hold for any interactions with the instance from its public interface.

The invariant is in the form of a const member function. The invariant is defined to hold if all the AssertExpressions within the invariant that are executed succeed.

struct Date
{
this(int d, int h)
{
day = d;    // days are 1..31
hour = h;   // hours are 0..23
}

invariant
{
assert(1 <= day && day <= 31);
assert(0 <= hour && hour < 24);
}

private:
int day;
int hour;
}

There may be multiple invariants in a struct. They are applied in lexical order.

Struct Invariants must hold at the exit of the struct constructor (if any), and at the entry of the struct destructor (if any).

Struct Invariants must hold at the entry and exit of all public or exported non-static member functions. The order of application of invariants is:

1. preconditions

2. invariant

3. function body

4. invariant

5. postconditions

The invariant need not hold if the struct instance is implicitly constructed using the default .init value.

If the invariant does not hold, then the program enters an invalid state.

Implementation Defined:

1. Whether the struct Invariant is executed at runtime or not. This is typically controlled with a compiler switch.

2. The behavior when the invariant does not hold is typically the same as for when AssertExpressions fail.

Undefined Behavior: happens if the invariant does not hold and execution continues.

Public or exported non-static member functions cannot be called from within an invariant.

struct Foo
{
public void f() { }
private void g() { }

invariant
{
f();  // error, cannot call public member function from invariant
g();  // ok, g() is not public
}
}

Best Practices:

1. Do not indirectly call exported or public member functions within a struct invariant, as this can result in infinite recursion.

2. Avoid reliance on side effects in the invariant. as the invariant may or may not be executed.

3. Avoid having mutable public fields of structs with invariants, as then the invariant cannot verify the public interface.

Identity Assignment Overload

While copy construction takes care of initializing an object from another object of the same type, assignment is defined as copying the contents of a source object over those of a destination object, calling the destination object's destructor if it has one in the process:

struct S { ... }  // S has postblit or destructor
S s;      // default construction of s
S t = s;  // t is copy-constructed from s
t = s;    // t is assigned from s

Struct assignment t=s is defined to be semantically equivalent to:

t.opAssign(s);

where opAssign is a member function of S:

ref S opAssign(ref S s)
{
S tmp = this;   // bitcopy this into tmp
this = s;       // bitcopy s into this
tmp.__dtor();   // call destructor on tmp
return this;
}

An identity assignment overload is required for a struct if one or more of these conditions hold:

- it has a destructor

- it has a postblit

- it has a field with an identity assignment overload

If an identity assignment overload is required and does not exist, an identity assignment overload function of the type ref S opAssign(ref S) will be automatically generated.

A user-defined one can implement the equivalent semantics, but can be more efficient.

One reason a custom opAssign might be more efficient is if the struct has a reference to a local buffer:

struct S
{
int[] buf;
int a;

ref S opAssign(ref const S s) return
{
a = s.a;
return this;
}

this(this)
{
buf = buf.dup;
}
}

Here, S has a temporary workspace buf[]. The normal postblit will pointlessly free and reallocate it. The custom opAssign will reuse the existing storage.

Alias This

AliasThis:
alias Identifier this ;
alias this = Identifier ;

An AliasThis declaration names a member to subtype. The Identifier names that member.

A struct or union instance can be implicitly converted to the AliasThis member.

struct S
{
int x;
alias x this;
}

int foo(int i) { return i * 2; }

void main()
{
S s;
s.x = 7;
int i = -s;
assert(i == -7);
i = s + 8;
assert(i == 15);
i = s + s;
assert(i == 14);
i = 9 + s;
assert(i == 16);
i = foo(s);  // implicit conversion to int
assert(i == 14);
}

If the member is a class or struct, undefined lookups will be forwarded to the AliasThis member.

class Foo
{
int baz = 4;
int get() { return 7; }
}

struct Bar
{
Foo foo;
alias foo this;
}

void main()
{
Bar bar = Bar(new Foo());
int i = bar.baz;
assert(i == 4);
i = bar.get();
assert(i == 7);
}

If the Identifier refers to a property member function with no parameters then conversions and undefined lookups are forwarded to the return value of the function.

struct S
{
int x;
@property int get()
{
return x * 2;
}
alias get this;
}

void main()
{
S s;
s.x = 2;
int i = s;
assert(i == 4);
}

If a struct declaration defines an opCmp or opEquals method, it will take precedence to that of the AliasThis member. Note that, unlike an opCmp method, an opEquals method is implicitly defined for a struct declaration if a user-defined one isn't provided. This means that if the AliasThis member's opEquals should be used, it must be explicitly defined:

struct S
{
int a;
bool opEquals(S rhs) const
{
return this.a == rhs.a;
}
}

struct T
{
int b;
S s;
alias s this;
}

void main()
{
S s1, s2;
T t1, t2;

assert(s1 == s2);      // calls S.opEquals
assert(t1 == t2);      // calls compiler generated T.opEquals that implements member-wise equality

assert(s1 == t1);      // calls s1.opEquals(t1.s);
assert(t1 == s1);      // calls t1.s.opEquals(s1);
}

struct U
{
int a;
bool opCmp(U rhs) const
{
return this.a < rhs.a;
}
}

struct V
{
int b;
U u;
alias u this;
}

void main()
{
U u1, u2;
V v1, v2;

assert(!(u1 < u2));    // calls U.opCmp
assert(!(v1 < v2));    // calls U.opCmp because V does not define an opCmp method
// so the alias this of v1 is employed; U.opCmp expects a
// paramter of type U, so alias this of v2 is used

assert(!(u1 < v1));    // calls u1.opCmp(v1.u);
assert(!(v1 < u1));    // calls v1.u.opCmp(v1);
}

Attributes are ignored for AliasThis.

A struct/union may only have a single AliasThis member.

Nested Structs

A struct is a nested struct if

1. it is declared inside the scope of a function, or

2. it is a templated struct with one or more template arguments that alias local functions.

A nested struct can have member functions. It has access to the context of its enclosing scope via a hidden field.

void foo()
{
int i = 7;
struct SS
{
int x,y;
int bar() { return x + i + 1; }
}
SS s;
s.x = 3;
s.bar(); // returns 11
}

The static attribute will prevent a struct from being nested. As such, the struct will not have access to its enclosing scope.

void foo()
{
int i = 7;
static struct SS
{
int x, y;
int bar()
{
return i; // error, SS is not a nested struct
}
}
}

Warning: For nested structs, .init is not the same as default construction.

Unions and Special Member Functions

Unions may not have postblits, destructors, or invariants.

Classes

Overview

The object-oriented features of D all come from classes. The class hierarchy has as its root the class Object. Object defines a minimum level of functionality that each derived class has, and a default implementation for that functionality.

Classes are programmer defined types. Support for classes are what make D an object oriented language, giving it encapsulation, inheritance, and polymorphism. D classes support the single inheritance paradigm, extended by adding support for interfaces. Class objects are instantiated by reference only.

A class can be exported, which means its name and all its non-private members are exposed externally to the DLL or EXE.

Class Declarations

A class declaration is defined:

ClassDeclaration:
class Identifier ;
class Identifier BaseClassListopt AggregateBody
ClassTemplateDeclaration

BaseClassList:
: SuperClassOrInterface
: SuperClassOrInterface , Interfaces

SuperClassOrInterface:
BasicType

Interfaces:
Interface
Interface , Interfaces

Interface:
BasicType

A class consists of:

- a super class

- interfaces

- dynamic fields

- static fields

- nested classes

- member functions

- static member functions

- Virtual Functions

- Constructors

- Destructors

- Class Invariants

- Operator Overloading

- other declarations (see DeclDef)

A class is defined:

class Foo
{
... members ...
}

Note: Unlike C++, there is no trailing ; after the closing } of the class definition. It is also not possible to declare a variable var inline:

class Foo { } var;

Instead, use:

class Foo { }
Foo var;

Access Control

Access to class members is controlled using visibility attributes. The default visibility attribute is public.

Super Class

All classes inherit from a super class. If one is not specified, a class inherits from Object. Object forms the root of the D class inheritance hierarchy.

class A { }     // A inherits from Object
class B : A { } // B inherits from A

Multiple class inheritance is not supported, however a class can inherit from multiple interfaces. If a super class is declared, it must come before any interfaces. Commas are used to separate inherited types.

Fields

Class members are always accessed with the . operator.

Members of a base class can be accessed by prepending the name of the base class followed by a dot:

class A { int a; int a2;}
class B : A { int a; }

void foo(B b)
{
b.a = 3;   // accesses field B.a
b.a2 = 4;  // accesses field A.a2
b.A.a = 5; // accesses field A.a
}

Implementation Defined: The D compiler is free to rearrange the order of fields in a class to optimally pack them. Consider the fields much like the local variables in a function - the compiler assigns some to registers and shuffles others around all to get the optimal stack frame layout. This frees the code designer to organize the fields in a manner that makes the code more readable rather than being forced to organize it according to machine optimization rules. Explicit control of field layout is provided by struct/union types, not classes.

Fields of extern(Objective-C) classes have a dynamic offset. That means that the base class can change (add or remove instance variables) without the subclasses needing to recompile or relink.

Class Properties

Class Properties

Property

Description

.tupleof

Symbol sequence of fields in the class.

Class Instance Properties

Property

Description

.classinfo

Information about the dynamic type of the class.

.outer

For a nested class instance, provides either the parent class instance, or the parent function's context pointer when there is no parent class.

.tupleof

The .tupleof property provides a symbol sequence of all the non-static fields in the class, excluding the initial hidden fields and the fields in the base class. When used on an instance, .tupleof gives an lvalue sequence.

The order of the fields in the tuple matches the order in which the fields are declared.

Note: .tupleof is not available for extern(Objective-C) classes due to their fields having a dynamic offset.

class Foo { int x; long y; }

static assert(__traits(identifier, Foo.tupleof[0]) == "x");
static assert(is(typeof(Foo.tupleof)[1] == long));

void main()
{
import std.stdio;

auto foo = new Foo;
foo.tupleof[0] = 1; // set foo.x to 1
foo.tupleof[1] = 2; // set foo.y to 2
foreach (ref x; foo.tupleof)
x++;
assert(foo.x == 2);
assert(foo.y == 3);

auto bar = new Foo;
bar.tupleof = foo.tupleof; // copy fields
assert(bar.x == 2);
assert(bar.y == 3);
}

Accessing Hidden Fields

The properties .__vptr and .__monitor give access to the class object's vtbl[] and monitor, respectively, but should not be used in user code. See the ABI for details.

See also: Context Pointer.

Field Properties

The .offsetof property gives the offset in bytes of the field from the beginning of the class instantiation. Note that the compiler can rearrange class field offsets. See the align attribute for a struct-based example.

Note: .offsetof is not available for fields of extern(Objective-C) classes due to their fields having a dynamic offset.

Member Functions (a.k.a. Methods)

Non-static member functions must be called on an instance of their class. They have an extra hidden parameter called this through which the class object's other members can be accessed. Inside the function body, the class instance members are in scope.

class C
{
int a;

void foo()
{
a = 3; // assign to `this.a`
}
}

auto c = new C;
c.foo();
assert(c.a == 3);

Non-static member functions can have, in addition to the usual FunctionAttributes, the attributes const, immutable, shared, inout, scope or return scope. These attributes apply to the hidden this parameter.

class C
{
int a;

void foo() const
{
a = 3; // error, 'this' is const
}
void foo() immutable
{
a = 3; // error, 'this' is immutable
}
C bar() @safe scope
{
return this; // error, 'this' is scope
}
}

Objective-C linkage

Static member functions with Objective-C linkage also have an extra hidden parameter called this through which the class object's other members can be accessed.

Member functions with Objective-C linkage have an additional hidden, anonymous, parameter which is the selector the function was called with.

Static member functions with Objective-C linkage are placed in a hidden nested metaclass as non-static member functions.

Synchronized Method Calls

Member functions of a (non-synchronized) class can be individually marked as synchronized. The class instance's monitor object will be locked when the method is called and unlocked when the call terminates.

A synchronized method can only be called on a shared class instance.

class C
{
void foo();
synchronized int bar();
}

void test(C c)
{
c.foo; // OK
//c.bar; // Error, `c` is not `shared`

shared C sc = new shared C;
//sc.foo; // Error, `foo` not callable using a `shared` object
sc.bar; // OK
}

See also SynchronizedStatement.

Synchronized Classes

Each member function of a synchronized class is implicitly synchronized. A static member function is synchronized on the classinfo object for the class, which means that one monitor is used for all static member functions for that synchronized class. For non-static functions of a synchronized class, the monitor used is part of the class object. For example:

synchronized class Foo
{
void bar() { ...statements... }
}

is equivalent to (as far as the monitors go):

synchronized class Foo
{
void bar()
{
synchronized (this) { ...statements... }
}
}

Note: bar uses a SynchronizedStatement.

Member fields of a synchronized class cannot be public:

synchronized class Foo
{
int foo;  // Error: public field
}

synchronized class Bar
{
private int bar;  // ok
}

Note: struct types cannot be marked synchronized.

Class Instantiation

Fields are by default initialized to the default initializer for their type (usually 0 for integer types and NaN for floating point types). If the field declaration has an optional Initializer that will be used instead of the default.

class Abc
{
int a;      // default initializer for a is 0
long b = 7; // default initializer for b is 7
float f;    // default initializer for f is NAN
}

void main()
{
Abc obj = new Abc;
assert(obj.b == 7);
}

The Initializer is evaluated at compile time.

This initialization is done before any constructors are called.

Instances of class objects are created with a NewExpression.

- A class C without a constructor (or with a nullary constructor) is instantiated using new C, without passing any arguments.

- Otherwise, a class can be instantiated with an argument list, e.g. new C(arguments). The arguments are passed to a constructor with a matching parameter list (and resolved like overloaded methods).

By default, a class instance is allocated on the garbage-collected heap. A scope class instance is allocated on the stack.

Constructors

A constructor is a special member function which is normally invoked by the compiler when the class instance is created.

Constructor:
this Parameters MemberFunctionAttributesopt FunctionBody
this Parameters MemberFunctionAttributesopt MissingFunctionBody
ConstructorTemplate

A constructor is declared with a function name of this and no return type:

class A
{
int i;

this() // constructor taking no arguments
{
i = 2; // initialize `this.i`
}

this(int i) // constructor taking an int argument
{
this.i = i; // initialize field `i` from parameter `i`
}
}

void main()
{
A a = new A; // instantiate A and call `A.this()`
assert(a.i == 2);

a = new A(3); // instantiate A and call `A.this(3)`
assert(a.i == 3);
}

Explicitly returning a value in a constructor is not allowed, however return; may be used to exit the function early.

Base Class Construction

Base class construction is done by calling the base class constructor by the name super:

class A { this(int y) { } }

class B : A
{
int j;
this()
{
...
super(3);  // call base constructor A.this(3)
...
}
}

Delegating Constructors

A constructor can call another constructor for the same class in order to share common initializations. This is called a delegating constructor:

class C
{
int j;
this()
{
...
}
this(int i)
{
this(); // delegating constructor call
j = i;
}
}

Restrictions

The following restrictions apply:

1. It is illegal for constructors to mutually call each other.

this() { this(1); }
this(int i) { this(); } // illegal, cyclic constructor calls

Implementation Defined: The compiler is not required to detect cyclic constructor calls.

Undefined Behavior: If the program executes with cyclic constructor calls.

2. If a constructor's code contains a delegating/base constructor call, all possible execution paths through the constructor must make exactly one of those calls:

this() { a || super(); } // illegal, 0 or 1 call

this() { (a) ? this(1) : super(); } // OK

this() { super(); this(1); } // illegal, 2 calls

this()
{
for (...)
{
super();  // illegal, inside loop
}
}

3. It is illegal to refer to this implicitly or explicitly prior to making a delegating/base constructor call.

4. Delegating/base constructor calls cannot appear after labels.

See also: field initialization.

Implicit Base Class Construction

If there is no constructor for a class, but there is a constructor for the base class, a default constructor is implicitly generated with the form:

this() { }

If no calls to a delegating constructor or super appear in a constructor, and the base class has a nullary constructor, a call to super() is inserted at the beginning of the constructor. If that base class has a constructor that requires arguments and no nullary constructor, a matching call to super is required.

Instantiation Process

The following steps happen:

1. Storage is allocated for the object. If this fails, rather than return null, an OutOfMemoryError is thrown. Thus, tedious checks for null references are unnecessary.

2. The raw data is statically initialized using the values provided in the class definition. The pointer to the vtbl[] (the array of pointers to virtual functions) is assigned. Constructors are passed fully formed objects for which virtual functions can be called. This operation is equivalent to doing a memory copy of a static version of the object onto the newly allocated one.

3. If there is a constructor defined for the class, the constructor matching the argument list is called.

4. If a delegating constructor is not called, a call to the base class's default constructor is issued.

5. The body of the constructor is executed.

6. If class invariant checking is turned on, the class invariant is called at the end of the constructor.

Constructor Attributes

Constructors can have one of these member function attributes: const, immutable, and shared. Construction of qualified objects will then be restricted to the implemented qualified constructors.

class C
{
this();   // non-shared mutable constructor
}

// create mutable object
C m = new C();

// create const object using mutable constructor
const C c2 = new const C();

// a mutable constructor cannot create an immutable object
// immutable C i = new immutable C();

// a mutable constructor cannot create a shared object
// shared C s = new shared C();

Constructors can be overloaded with different attributes.

class C
{
this();               // non-shared mutable constructor
this() shared;        // shared mutable constructor
this() immutable;     // immutable constructor
}

C m = new C();
shared s = new shared C();
immutable i = new immutable C();

Pure Constructors

If the constructor can create a unique object (e.g. if it is pure), the object can be implicitly convertible to any qualifiers.

class C
{
this() pure;
// Based on the definition, this creates a mutable object. But the
// created object cannot contain any mutable global data.
// Therefore the created object is unique.

this(int[] arr) immutable pure;
// Based on the definition, this creates an immutable object. But
// the argument int[] never appears in the created object so it
// isn't implicitly convertible to immutable. Also, it cannot store
// any immutable global data.
// Therefore the created object is unique.
}

immutable i = new immutable C();           // this() pure is called
shared s = new shared C();                 // this() pure is called
C m = new C([1,2,3]);       // this(int[]) immutable pure is called

Field initialization inside a constructor

In a constructor body, the first instance of field assignment is its initialization.

class C
{
int num;
this()
{
num = 1;  // initialization
num = 2;  // assignment
}
}

If the field type has an opAssign method, it will not be used for initialization.

struct A
{
this(int n) {}
void opAssign(A rhs) {}
}
class C
{
A val;
this()
{
val = A(1);  // val is initialized to the value of A(1)
val = A(2);  // rewritten to val.opAssign(A(2))
}
}

If the field type is not mutable, multiple initialization will be rejected.

class C
{
immutable int num;
this()
{
num = 1;  // OK
num = 2;  // Error: multiple field initialization
}
}

If the field is initialized on one path, it must be initialized on all paths.

class C
{
immutable int num;
immutable int ber;
this(int i)
{
if (i)
num = 3;   // initialization
else
num = 4;   // initialization
}
this(long j)
{
j ? (num = 3) : (num = 4); // ok
j || (ber = 3);  // error, intialized on only one path
j && (ber = 3);  // error, intialized on only one path
}
}

A field initialization may not appear in a loop or after a label.

class C
{
immutable int num;
immutable string str;
this()
{
foreach (i; 0..2)
{
num = 1;    // Error: field initialization not allowed in loops
}
size_t i = 0;
Label:
str = "hello";  // Error: field initialization not allowed after labels
if (i++ < 2)
goto Label;
}
}

If a field's type has disabled default construction, then it must be initialized in the constructor.

struct S { int y; @disable this(); }

class C
{
S s;
this(S t) { s = t; }    // ok
this(int i) { this(); } // ok
this() { }              // error, s not initialized
}

Destructors

Destructor:
~ this ( ) MemberFunctionAttributesopt FunctionBody
~ this ( ) MemberFunctionAttributesopt MissingFunctionBody

The destructor function is called when:

- A live object is deleted by the garbage collector

- A live scope class instance goes out of scope

- destroy is called on the object

Example:

import std.stdio;

class Foo
{
~this() // destructor for Foo
{
writeln("dtor");
}
}

void main()
{
auto foo = new Foo;
destroy(foo);
writeln("end");
}

- Only one destructor can be declared per class, although other destructors can be mixed in.

- A destructor does not have any parameters or attributes.

- A destructor is always virtual.

The destructor is expected to release any non-GC resources held by the object.

The program can explicitly call the destructor of a live object immediately with destroy. The runtime marks the object so the destructor is never called twice.

The destructor for the super class automatically gets called when the destructor ends. There is no way to call the super class destructor explicitly.

Implementation Defined: The garbage collector is not guaranteed to run the destructor for all unreferenced objects.

Important: The order in which the garbage collector calls destructors for unreferenced objects is not specified. This means that when the garbage collector calls a destructor for an object of a class that has members which are references to garbage collected objects, those references may no longer be valid. This means that destructors cannot reference sub objects.

Note: This rule does not apply to a scope class instance or an object destructed with destroy, as the destructor is not being run during a garbage collection cycle, meaning all references are valid.

Objects referenced from the static data segment never get collected by the GC.

Static Constructors and Destructors

Static Constructors

StaticConstructor:
static this ( ) MemberFunctionAttributesopt FunctionBody
static this ( ) MemberFunctionAttributesopt MissingFunctionBody

A static constructor is a function that performs initializations of thread local data before the main() function gets control for the main thread, and upon thread startup.

Static constructors are used to initialize static class members with values that cannot be computed at compile time.

Static constructors in other languages are built implicitly by using member initializers that can't be computed at compile time. The trouble with this stems from not having good control over exactly when the code is executed, for example:

class Foo
{
static int a = b + 1;
static int b = a * 2;
}

What values do a and b end up with, what order are the initializations executed in, what are the values of a and b before the initializations are run, is this a compile error, or is this a runtime error? Additional confusion comes from it not being obvious if an initializer is static or dynamic.
D makes this simple. All member initializations must be determinable by the compiler at compile time, hence there is no order-of-evaluation dependency for member initializations, and it is not possible to read a value that has not been initialized. Dynamic initialization is performed by a static constructor, defined with a special syntax static this().

class Foo
{
static int a;         // default initialized to 0
static int b = 1;
static int c = b + a; // error, not a constant initializer

static this()    // static constructor
{
a = b + 1;          // a is set to 2
b = a * 2;          // b is set to 4
}
}

If main() or the thread returns normally, (does not throw an exception), the static destructor is added to the list of functions to be called on thread termination.

Static constructors have empty parameter lists.

Static constructors within a module are executed in the lexical order in which they appear. All the static constructors for modules that are directly or indirectly imported are executed before the static constructors for the importer.

The static in the static constructor declaration is not an attribute, it must appear immediately before the this:

class Foo
{
static this() { ... } // a static constructor
static private this() { ... } // Error: not a static constructor
static
{
this() { ... }      // not a static constructor
}
static:
this() { ... }      // not a static constructor
}

Static Destructors

StaticDestructor:
static ~ this ( ) MemberFunctionAttributesopt FunctionBody

A static destructor is defined as a special static function with the syntax static ~this().

class Foo
{
static ~this() // static destructor
{
}
}

A static destructor gets called on thread termination, but only if the static constructor completed successfully. Static destructors have empty parameter lists. Static destructors get called in the reverse order that the static constructors were called in.

The static in the static destructor declaration is not an attribute, it must appear immediately before the ~this:

class Foo
{
static ~this() { ... }  // a static destructor
static private ~this() { ... } // Error: not a static destructor
static
{
~this() { ... }  // not a static destructor
}
static:
~this() { ... }  // not a static destructor
}

Shared Static Constructors

SharedStaticConstructor:
shared static this ( ) MemberFunctionAttributesopt FunctionBody
shared static this ( ) MemberFunctionAttributesopt MissingFunctionBody

Shared static constructors are executed before any StaticConstructors, and are intended for initializing any shared global data.

Shared Static Destructors

SharedStaticDestructor:
shared static ~ this ( ) MemberFunctionAttributesopt FunctionBody
shared static ~ this ( ) MemberFunctionAttributesopt MissingFunctionBody

Shared static destructors are executed at program termination in the reverse order that SharedStaticConstructors were executed.

Class Invariants

Invariant:
invariant ( ) BlockStatement
invariant BlockStatement
invariant ( AssertArguments ) ;

Class Invariants specify the relationships among the members of a class instance. Those relationships must hold for any interactions with the instance from its public interface.

The invariant is in the form of a const member function. The invariant is defined to hold if all the AssertExpressions within the invariant that are executed succeed.

class Date
{
this(int d, int h)
{
day = d;    // days are 1..31
hour = h;   // hours are 0..23
}

invariant
{
assert(1 <= day && day <= 31);
assert(0 <= hour && hour < 24);
}

private:
int day;
int hour;
}

Any class invariants for base classes are applied before the class invariant for the derived class.

There may be multiple invariants in a class. They are applied in lexical order.

Class Invariants must hold at the exit of the class constructor (if any), and at the entry of the class destructor (if any).

Class Invariants must hold at the entry and exit of all public or exported non-static member functions. The order of application of invariants is:

1. preconditions

2. invariant

3. function body

4. invariant

5. postconditions

If the invariant does not hold, then the program enters an invalid state.

Implementation Defined:

1. Whether the class Invariant is executed at runtime or not. This is typically controlled with a compiler switch.

2. The behavior when the invariant does not hold is typically the same as for when AssertExpressions fail.

Undefined Behavior: happens if the invariant does not hold and execution continues.

Public or exported non-static member functions cannot be called from within an invariant.

class Foo
{
public void f() { }
private void g() { }

invariant
{
f();  // error, cannot call public member function from invariant
g();  // ok, g() is not public
}
}

Best Practices:

1. Do not indirectly call exported or public member functions within a class invariant, as this can result in infinite recursion.

2. Avoid reliance on side effects in the invariant. as the invariant may or may not be executed.

3. Avoid having mutable public fields of classes with invariants, as then the invariant cannot verify the public interface.

Scope Classes

Note: Scope classes have been deprecated. See also scope class instances.

A scope class is a class with the scope attribute, as in:

scope class Foo { ... }

The scope characteristic is inherited, so any classes derived from a scope class are also scope.

A scope class reference can only appear as a function local variable. It must be declared as being scope:

scope class Foo { ... }

void func()
{
Foo f;    // error, reference to scope class must be scope
scope Foo g = new Foo(); // correct
}

When a scope class reference goes out of scope, the destructor (if any) for it is automatically called. This holds true even if the scope was exited via a thrown exception.

Abstract Classes

An abstract member function must be overridden by a derived class. Only virtual member functions may be declared abstract; non-virtual member functions and free-standing functions cannot be declared abstract.

A class is abstract if any of its virtual member functions are declared abstract or if they are defined within an abstract attribute. Note that an abstract class may also contain non-virtual member functions. Abstract classes cannot be instantiated directly. They can only be instantiated as a base class of another, non-abstract, class.

class C
{
abstract void f();
}

auto c = new C; // error, C is abstract

class D : C {}

auto d = new D; // error, D is abstract

class E : C
{
override void f() {}
}

auto e = new E; // OK

Member functions declared as abstract can still have function bodies. This is so that even though they must be overridden, they can still provide ‘base class functionality’, e.g. through super.foo() in a derived class. Note that the class is still abstract and cannot be instantiated directly.

A class can be declared abstract:

abstract class A
{
// ...
}

auto a = new A; // error, A is abstract

class B : A {}

auto b = new B; // OK

Final Classes

Final classes cannot be subclassed:

final class A { }
class B : A { }  // error, class A is final

Methods of a final class are always final.

Nested Classes

A nested class is a class that is declared inside the scope of a function or another class. A nested class has access to the variables and other symbols of the classes and functions it is nested inside:

class Outer
{
int m;

class Inner
{
int foo()
{
return m;   // Ok to access member of Outer
}
}
}

void func()
{
int m;

class Inner
{
int foo()
{
return m; // Ok to access local variable m of func()
}
}
}

Static Nested Classes

If a nested class has the static attribute, then it can not access variables of the enclosing scope that are local to the stack or need a this reference:

class Outer
{
int m;
static int n;

static class Inner
{
int foo()
{
return m;   // Error, Inner is static and m needs a this
return n;   // Ok, n is static
}
}
}

void func()
{
int m;
static int n;

static class Inner
{
int foo()
{
return m;   // Error, Inner is static and m is local to the stack
return n;   // Ok, n is static
}
}
}

Context Pointer

Non-static nested classes work by containing an extra hidden member (called the context pointer) that is the frame pointer of the enclosing function if it is nested inside a function, or the this reference of the enclosing class's instance if it is nested inside a class.

When a non-static nested class is instantiated, the context pointer is assigned before the class's constructor is called, therefore the constructor has full access to the enclosing variables. A non-static nested class can only be instantiated when the necessary context pointer information is available:

class Outer
{
class Inner { }

static class SInner { }
}

void main()
{
Outer o = new Outer;        // Ok
//Outer.Inner oi = new Outer.Inner; // Error, no 'this' for Outer
Outer.SInner os = new Outer.SInner; // Ok
}

void main()
{
class Nested { }

Nested n = new Nested;      // Ok

static f()
{
//Nested sn = new Nested; // Error, no 'this' for Nested
}
}

Explicit Instantiation

A this reference can be supplied to the creation of an inner class instance by prefixing it to the NewExpression:

class Outer
{
int a;

class Inner
{
int foo()
{
return a;
}
}
}

void main()
{
Outer o = new Outer;
o.a = 3;
Outer.Inner oi = o.new Inner;
assert(oi.foo() == 3);
}

Here o supplies the this reference to the inner class instance of Outer.

outer Property

For a nested class instance, the .outer property provides the this reference of the enclosing class's instance. If there is no accessible parent class instance, the property provides a void* to the enclosing function frame.

class Outer
{
class Inner1
{
Outer getOuter()
{
return this.outer;
}
}

void foo()
{
Inner1 i = new Inner1;
assert(i.getOuter() is this);
}
}

class Outer
{
void bar()
{
// x is referenced from nested scope, so
// bar makes a closure environment.
int x = 1;

class Inner2
{
Outer getOuter()
{
x = 2;
// The Inner2 instance has access to the function frame
// of bar as a static frame pointer, but .outer returns
// the enclosing Outer class instance property.
return this.outer;
}
}

Inner2 i = new Inner2;
assert(i.getOuter() is this);
}
}

class Outer
{
// baz cannot access an instance of Outer
static void baz()
{
// make a closure environment
int x = 1;

class Inner3
{
void* getOuter()
{
x = 2;
// There's no accessible enclosing class instance, so the
// .outer property returns the function frame of baz.
return this.outer;
}
}

Inner3 i = new Inner3;
assert(i.getOuter() !is null);
}
}

Anonymous Nested Classes

An anonymous nested class is both defined and instantiated with a NewAnonClassExpression:

NewAnonClassExpression:
new class ConstructorArgsopt AnonBaseClassListopt AggregateBody

ConstructorArgs:
( NamedArgumentListopt )

AnonBaseClassList:
SuperClassOrInterface
SuperClassOrInterface , Interfaces

which is equivalent to:

class Identifier : AnonBaseClassList AggregateBody
// ...
new Identifier ConstructorArgs

where Identifier is the name generated for the anonymous nested class.

interface I
{
void foo();
}

auto obj = new class I
{
void foo()
{
writeln("foo");
}
};
obj.foo();

Const, Immutable and Shared Classes

If a ClassDeclaration has a const, immutable or shared storage class, then it is as if each member of the class was declared with that storage class. If a base class is const, immutable or shared, then all classes derived from it are also const, immutable or shared.

Interfaces

Interface Declarations

An Interface describes a list of functions that a class which inherits from the interface must implement.

InterfaceDeclaration:
interface Identifier ;
interface Identifier BaseInterfaceListopt AggregateBody
InterfaceTemplateDeclaration

BaseInterfaceList:
: Interfaces

Implementation Defined:

Specialized interfaces may be supported:

1. COM Interfaces are binary compatible with COM/OLE/ActiveX objects for Windows.

2. C++ Interfaces are binary compatible with C++ abstract classes.

3. Objective-C Interfaces are binary compatible with Objective-C protocols.

A class that implements an interface can be implicitly converted to a reference to that interface.

Interfaces cannot derive from classes; only from other interfaces. Classes cannot derive from an interface multiple times.

interface I
{
void foo();
}

class A : I, I  // error, duplicate interface
{
}

An instance of an interface cannot be created.

interface I
{
void foo();
}

...

I iface = new I();  // error, cannot create instance of interface

Interface Method Bodies

Virtual interface member functions do not have implementations. Interfaces are expected to implement static or final functions.

interface I
{
void bar() { }  // error, implementation not allowed
static void foo() { } // ok
final void abc() { } // ok
}

Interfaces can have function templates in the members. All instantiated functions are implicitly final.

interface I
{
void foo(T)() { }  // ok, it's implicitly final
}

Classes that inherit from an interface may not override final or static interface member functions.

interface I
{
void bar();
static void foo() { }
final void abc() { }
}

class C : I
{
void bar() { } // ok
void foo() { } // error, cannot override static I.foo()
void abc() { } // error, cannot override final I.abc()
}

Implementing Interfaces

All virtual interface functions must be defined in a class that inherits from that interface:

interface I
{
void foo();
}

class A : I
{
void foo() { }  // ok, provides implementation
}

class B : I
{
int foo() { }   // error, no `void foo()` implementation
}

Interfaces can be inherited from a base class, and interface functions overridden:

interface I
{
int foo();
}

class A : I
{
int foo() { return 1; }
}

class B : A
{
override int foo() { return 2; }
}

B b = new B();
assert(b.foo() == 2);

I i = b;    // ok since B inherits A's I implementation
assert(i.foo() == 2);

Reimplementing Interfaces

Interfaces can be reimplemented in derived classes:

interface I
{
int foo();
}

class A : I
{
int foo() { return 1; }
}

class B : A, I
{
override int foo() { return 2; }
}

B b = new B();
assert(b.foo() == 2);
I i = b;
assert(i.foo() == 2);

A a = b;
I i2 = a;
assert(i2.foo() == 2); // i2 has A's virtual pointer for foo which points to B.foo

A reimplemented interface must implement all the interface functions, it does not inherit them from a super class:

interface I
{
int foo();
}

class A : I
{
int foo() { return 1; }
}

class B : A, I
{
}       // error, no foo() for interface I

Interface Method Contracts

Interface member functions can have contracts even though there is no body for the function. The contracts are inherited by any class member function that implements that interface member function.

interface I
{
int foo(int i)
in { assert(i > 7); }
out (result) { assert(result & 1); }

void bar();
}

Const and Immutable Interfaces

If an interface has const or immutable storage class, then all members of the interface are const or immutable. This storage class is not inherited.

COM Interfaces

A variant on interfaces is the COM interface. A COM interface is designed to map directly onto a Windows COM object. Any COM object can be represented by a COM interface, and any D object with a COM interface can be used by external COM clients.

A COM interface is defined as one that derives from the interface core.sys.windows.com.IUnknown. A COM interface differs from a regular D interface in that:

- It derives from the interface core.sys.windows.com.IUnknown.

- It cannot be the argument to destroy.

- References cannot be upcast to the enclosing class object, nor can they be downcast to a derived interface. Implement QueryInterface() for that interface in standard COM fashion to convert to another COM interface.

- Classes derived from COM interfaces are COM classes.

- The default linkage for member functions of COM classes is extern(System).
Note: To implement or override any base-class methods of D interfaces or classes (ones which do not inherit from IUnknown), explicitly mark them as having the extern(D) linkage.

import core.sys.windows.windows;
import core.sys.windows.com;

interface IText
{
void write();
}

abstract class Printer : IText
{
void print() { }
}

class C : Printer, IUnknown
{
// Implements the IText `write` class method.
extern(D) void write() { }

// Overrides the Printer `print` class method.
extern(D) override void print() { }

// Overrides the Object base class `toString` method.
extern(D) override string toString() { return "Class C"; }

// Methods of class implementing the IUnknown interface have
// the extern(System) calling convention by default.
HRESULT QueryInterface(const(IID)*, void**);
uint AddRef();
uint Release();
}

The same applies to other Object methods such as opCmp, toHash, etc.

- The first member of the COM vtbl[] is not the pointer to the InterfaceInfo, but the first virtual function pointer.

See also Modern COM Programming in D

C++ Interfaces

C++ interfaces are interfaces declared with C++ linkage:

extern (C++) interface Ifoo
{
void foo();
void bar();
}

which is meant to correspond with the following C++ declaration:

class Ifoo
{
virtual void foo();
virtual void bar();
};

Any interface that derives from a C++ interface is also a C++ interface. A C++ interface differs from a D interface in that:

- It cannot be the argument to destroy.

- References cannot be upcast to the enclosing class object, nor can they be downcast to a derived interface.

- The C++ calling convention is the default convention for its member functions, rather than the D calling convention.

- The first member of the vtbl[] is not the pointer to the Interface, but the first virtual function pointer.

Enums

EnumDeclaration:
enum Identifier EnumBody
enum Identifier : EnumBaseType EnumBody
AnonymousEnumDeclaration

EnumBaseType:
Type

EnumBody:
{ EnumMembers }
;

EnumMembers:
EnumMember
EnumMember ,
EnumMember , EnumMembers

EnumMember:
EnumMemberAttributesopt Identifier
EnumMemberAttributesopt Identifier = AssignExpression

EnumMemberAttributes:
EnumMemberAttribute
EnumMemberAttribute EnumMemberAttributes

EnumMemberAttribute:
DeprecatedAttribute
UserDefinedAttribute
@ disable

AnonymousEnumDeclaration:
enum : EnumBaseType { EnumMembers }
enum { AnonymousEnumMembers }

AnonymousEnumMembers:
AnonymousEnumMember
AnonymousEnumMember ,
AnonymousEnumMember , AnonymousEnumMembers

AnonymousEnumMember:
EnumMember
EnumMemberAttributesopt Type Identifier = AssignExpression

Enum declarations are used to define a group of constants.

Named Enums

Named enums are used to declare related constants and group them by giving them a unique type. The EnumMembers are declared in the scope of the named enum. The named enum declares a new type, and all the EnumMembers have that type.

This defines a new type X which has values X.A = 0, X.B = 1, X.C = 2:

enum X { A, B, C }  // named enum

A variable can be of named enum type. Its default initializer is the first member defined for the enum type.

enum X { A = 3, B, C }

X x;
assert(x == X.A);
x |= X.B;
assert(x & X.A);

The result type of a binary operation on a named enum is defined here.

If the EnumBaseType is not explicitly set, and the first EnumMember has an AssignExpression, it is set to the type of that AssignExpression. Otherwise, it defaults to type int.

- A named enum member can be implicitly cast to its EnumBaseType.

- An EnumBaseType instance cannot be implicitly cast to a named enum type.

enum Foo { E }

Foo f;
int i;
i = f;           // OK
f = i;           // error
f = cast(Foo)i;  // OK
f = 0;           // error
f = Foo.E;       // OK

A named enum member cannot declare its own Type.

See also: final switch on a named enum.

Enum Member Values

The value of an EnumMember is given by its AssignExpression if present. If there is no AssignExpression and it is the first EnumMember, its value is converted to EnumBaseType from 0. If there is no AssignExpression and it is not the first EnumMember, it is given the value of the previous EnumMember + 1:

- If the value of the previous EnumMember is EnumBaseType.max, it is an error. This prevents value overflow. It is an error if the previous member cannot be compared with EnumBaseType.max at compile-time.

- It is an error if the base type does not define a compile-time evaluable + 1 operation.

- If the value of the previous EnumMember + 1 is the same as the value of the previous EnumMember, it is an error. (This can happen with floating point types).

enum E : char
{
a,
b = char.max,
c // overflow
}

static assert(E.a == 0);

All EnumMembers are in scope for the AssignExpressions.

enum A = 3;
enum B
{
A = A // error, circular reference
}
enum C
{
A = B,  // A = 4
B = D,  // B = 4
C = 3,  // C = 3
D       // D = 4
}
enum E : C
{
E1 = C.D,
E2      // error, C.D is C.max
}

An empty enum body signifies an opaque enum - the enum members are unknown.

enum X;          // opaque enum
writeln(X.init); // error: enum X is opaque and has no default initializer

Enum Properties

Enum properties only exist for named enums.

Named Enum Properties

.init

First enum member value

.min

Smallest enum member value

.max

Largest enum member value

.sizeof

Size of storage for an enumerated value

For example:

enum X { A = 3, B = 1, C = 4, D, E = 2 }
X.init   // is X.A
X.min    // is X.B
X.max    // is X.D
X.sizeof // is same as int.sizeof

The EnumBaseType of named enums must support comparison in order to compute the .max and .min properties.

Enum Copying and Assignment

A named enum type never has a copy constructor, postblit, or identity assignment overload, even if one is defined by its EnumBaseType.

When copying a named enum value whose base type is a struct with a copy constructor, the copy constructor is not called:

struct S
{
this(ref S rhs) { assert(0); }
}

enum E : S { A = S.init }

void main()
{
E e1;
E e2 = e1; // ok - copy constructor not called
}

When copying a named enum value whose base type is a struct with a postblit, the postblit is not called:

struct S
{
this(this) { assert(0); }
}

enum E : S { A = S.init }

void main()
{
E e1;
E e2 = e1; // ok - postblit not called
}

When assigning a named enum value to another object of the same type, if the base type of those values is a struct with an identity assignment overload, the identity assignment overload is not called:

struct S
{
void opAssign(S rhs) { assert(0); }
}

enum E : S { A = S.init }

void main()
{
E e1, e2;
e2 = e1; // ok - opAssign not called
}

Anonymous Enums

If the enum Identifier is not present, then the enum is an anonymous enum, and the EnumMembers are declared in the scope the EnumDeclaration appears in. No new type is created.

The EnumMembers can have different types. Those types are given by the first of:

1. The Type, if present. Types are not permitted when an EnumBaseType is present.

2. The EnumBaseType, if present.

3. The type of the AssignExpression, if present.

4. The type of the previous EnumMember, if present.

5. int

enum { A, B, C }  // anonymous enum

Defines the constants A = 0, B = 1, C = 2, all of type int.

Enums must have at least one member.

The value of an EnumMember is given by its AssignExpression if present. If there is no AssignExpression and it is the first EnumMember, its value is the .init property of the EnumMember's type. If there is no AssignExpression and it is not the first EnumMember, it is given the value of the previous EnumMember + 1:

- If the value of the previous EnumMember is the .max property of the previous EnumMember's type, it is an error. This prevents value overflow. It is an error if the previous member cannot be compared with its .max property at compile-time.

- It is an error if the type of the previous member does not define a compile-time evaluable +1 operation.

- If the value of the previous EnumMember+1 is the same as the value of the previous EnumMember, it is an error. (This can happen with floating point types).

All EnumMembers are in scope for the AssignExpressions.

enum { A, B = 5 + 7, C, D = 8 + C, E }

Sets A = 0, B = 12, C = 13, D = 21, and E = 22, all of type int.

enum : long { A = 3, B }

Sets A = 3 and B = 4, both of type long.

enum : string
{
A = "hello",
B = "betty",
C     // error, cannot add 1 to "betty"
}

enum
{
A = 1.2f,  // A is 1.2f of type float
B,         // B is 2.2f of type float
int C = 3, // C is 3 of type int
D          // D is 4 of type int
}

Single Member Anonymous Enum

If there is only one member of an anonymous enum, the { } can be omitted. Gramatically speaking, this is an AutoDeclaration.

enum i = 4;      // i is 4 of type int
enum long l = 3; // l is 3 of type long

Manifest Constants

Enum members are manifest constants, which exist only at compile-time.

Manifest constants are not lvalues, meaning their address cannot be taken. They exist only in the memory of the compiler.

enum size = __traits(classInstanceSize, Foo);  // evaluated at compile-time

The initializer for a manifest constant is evaluated using compile-time function evaluation.

template Foo(T)
{
// Not bad, but the 'size' variable will be located in the executable.
const size_t size = T.sizeof;       // evaluated at compile-time

// ... use of 'size' at compile-time ...
}

template Bar(T)
{
// Better, the manifest constant has no runtime location in the executable.
enum size_t size = T.sizeof;        // evaluated at compile-time

// ... use of 'size' at compile time ...

// Taking the address of Foo!T.size also causes it to go into the exe file.
auto p = &Foo!T.size;
}

Type Qualifiers

Type qualifiers modify a type by applying a TypeCtor. TypeCtors are: const, immutable, shared, and inout. Each applies transitively to all subtypes.

Const and Immutable

When examining a data structure or interface, it is very helpful to be able to easily tell which data can be expected to not change, which data might change, and who may change that data. This is done with the aid of the language typing system. Data can be marked as const or immutable, with the default being changeable (or mutable).

immutable applies to data that cannot change. Immutable data values, once constructed, remain the same for the duration of the program's execution. Immutable data can be placed in ROM (Read Only Memory) or in memory pages marked by the hardware as read only. Since immutable data does not change, it enables many opportunities for program optimization, and has applications in functional style programming.

const applies to data that cannot be changed by the const reference to that data. It may, however, be changed by another reference to that same data. Const finds applications in passing data through interfaces that promise not to modify them.

Both immutable and const are transitive, which means that any data reachable through an immutable reference is also immutable, and likewise for const.

Immutable Storage Class

The simplest immutable declarations use it as a storage class. It can be used to declare manifest constants.

immutable int x = 3;  // x is set to 3
x = 4;        // error, x is immutable
char[x] s;    // s is an array of 3 chars

The type can be inferred from the initializer:

immutable y = 4; // y is of type int
y = 5;           // error, y is immutable

If the initializer is not present, the immutable can be initialized from the corresponding constructor:

immutable int z;
void test()
{
z = 3; // error, z is immutable
}
static this()
{
z = 3; // ok, can set immutable that doesn't
// have static initializer
}

The initializer for a non-local immutable declaration must be evaluatable at compile time:

int foo(int f) { return f * 3; }
int i = 5;
immutable x = 3 * 4;      // ok, 12
immutable y = i + 1;      // error, cannot evaluate at compile time
immutable z = foo(2) + 1; // ok, foo(2) can be evaluated at compile time, 7

The initializer for a non-static local immutable declaration is evaluated at run time:

int foo(int f)
{
immutable x = f + 1;  // evaluated at run time
x = 3;                // error, x is immutable
}

Because immutable is transitive, data referred to by an immutable is also immutable:

immutable char[] s = "foo";
s[0] = 'a';  // error, s refers to immutable data
s = "bar";   // error, s is immutable

Immutable declarations can appear as lvalues, i.e. they can have their address taken, and occupy storage.

Const Storage Class

A const declaration is exactly like an immutable declaration, with the following differences:

- Any data referenced by the const declaration cannot be changed from the const declaration, but it might be changed by other references to the same data.

- The type of a const declaration is itself const.

Immutable Type

Data that will never change its value can be typed as immutable. The immutable keyword can be used as a type qualifier:

immutable(char)[] s = "hello";

The immutable applies to the type within the following parentheses. So, while s can be assigned new values, the contents of s[] cannot be:

s[0] = 'b';  // error, s[] is immutable
s = null;    // ok, s itself is not immutable

Immutability is transitive, meaning it applies to anything that can be referenced from the immutable type:

immutable(char*)** p = ...;
p = ...;        // ok, p is not immutable
*p = ...;       // ok, *p is not immutable
**p = ...;      // error, **p is immutable
***p = ...;     // error, ***p is immutable

Immutable used as a storage class is equivalent to using immutable as a type qualifier for the entire type of a declaration:

immutable int x = 3;   // x is typed as immutable(int)
immutable(int) y = 3;  // y is immutable

Creating Immutable Data

The first way is to use a literal that is already immutable, such as string literals. String literals are always immutable.

auto s = "hello";   // s is immutable(char)[5]
char[] p = "world"; // error, cannot implicitly convert immutable
// to mutable

The second way is to cast data to immutable. When doing so, it is up to the programmer to ensure that any mutable references to the same data are not used to modify the data after the cast.

char[] s = ['a'];
s[0] = 'b'; // ok
immutable(char)[] p = cast(immutable)s; // ok, if data is not mutated
// through s anymore
s[0] = 'c'; // undefined behavior
immutable(char)[] q = cast(immutable)s.dup; // always ok, unique reference

char[][] s2 = [['a', 'b'], ['c', 'd']];
immutable(char[][]) p2 = cast(immutable)s2.dup; // dangerous, only the first
// level of elements is unique
s2[0] = ['x', 'y']; // ok, doesn't affect p2
s2[1][0] = 'z'; // undefined behavior
immutable(char[][]) q2 = [s2[0].dup, s2[1].dup]; // always ok, unique references

The .idup property is a convenient way to create an immutable copy of an array:

auto p = s.idup;
p[0] = ...;       // error, p[] is immutable

Removing Immutable or Const with a Cast

An immutable or const type qualifier can be removed with a cast:

immutable int* p = ...;
int* q = cast(int*)p;

This does not mean, however, that one can change the data:

*q = 3; // allowed by compiler, but result is undefined behavior

The ability to cast away immutable-correctness is necessary in some cases where the static typing is incorrect and not fixable, such as when referencing code in a library one cannot change. Casting is, as always, a blunt and effective instrument, and when using it to cast away immutable-correctness, one must assume the responsibility to ensure the immutability of the data, as the compiler will no longer be able to statically do so.

Undefined Behavior: casting away a const qualifier and then mutating it, even when the referenced data is mutable. This is so that compilers and programmers can make assumptions based on const alone. For example, here it may be assumed that f does not alter x:

void f(const int* a);
void main()
{
int x = 1;
f(&x);
assert(x == 1); // guaranteed to hold
}

Immutable Member Functions

Immutable member functions are guaranteed that the object and anything referred to by the this reference is immutable. They are declared as:

struct S
{
int x;

void foo() immutable
{
x = 4;      // error, x is immutable
this.x = 4; // error, x is immutable
}
}

Note that using immutable on the left hand side of a method does not apply to the return type:

struct S
{
immutable int[] bar()  // bar is still immutable, return type is not!
{
}
}

To make the return type immutable, surround it with parentheses:

struct S
{
immutable(int[]) bar()  // bar is now mutable, return type is immutable.
{
}
}

To make both the return type and the method immutable, write:

struct S
{
immutable(int[]) bar() immutable
{
}
}

Const Type

Const types are like immutable types, except that const forms a read-only view of data. Other aliases to that same data may change it at any time.

Const Member Functions

Const member functions are functions that are not allowed to change any part of the object through the member function's this reference.

Inout

Functions that differ only in whether the parameters are mutable, const or immutable, and have corresponding mutable, const or immutable return types, can be combined into one function using the inout type constructor. Consider the following overload set:

int[] slice(int[] a, int x, int y) { return a[x .. y]; }

const(int)[] slice(const(int)[] a, int x, int y) { return a[x .. y]; }

immutable(int)[] slice(immutable(int)[] a, int x, int y) { return a[x .. y]; }

The code generated by each of these functions is identical. The inout type constructor can combine them into one function:

inout(int)[] slice(inout(int)[] a, int x, int y) { return a[x .. y]; }

The inout keyword forms a wildcard that stands in for mutable, const, immutable, inout, or inout const. When calling the function, the inout state of the return type is changed to match that of the argument type passed to the inout parameter.

inout can also be used as a type constructor inside a function that has a parameter declared with inout. The inout state of a type declared with inout is changed to match that of the argument type passed to the inout parameter:

inout(int)[] asymmetric(inout(int)[] input_data)
{
inout(int)[] r = input_data;
while (r.length > 1 && r[0] == r[$-1])
r = r[1..$-1];
return r;
}

Inout types can be implicitly converted to const or inout const, but to nothing else. Other types cannot be implicitly converted to inout. Casting to or from inout is not allowed in @safe functions.

void f(inout int* ptr)
{
const int* p = ptr;
int* q = ptr; // error
immutable int* r = ptr; // error
}

Matching an inout Parameter

A set of arguments to a function with inout parameters is considered a match if any inout argument types match exactly, or:

1. No argument types are composed of inout types.

2. A mutable, const or immutable argument type can be matched against each corresponding parameter inout type.

If such a match occurs, inout is considered the common qualifier of the matched qualifiers. If more than two parameters exist, the common qualifier calculation is recursively applied.

Common qualifier of the two type qualifiers

mutable

const

immutable

inout

inout const

mutable (= m)

m

c

c

c

c

const (= c)

c

c

c

c

c

immutable (= i)

c

c

i

wc

wc

inout (= w)

c

c

wc

w

wc

inout const (= wc)

c

c

wc

wc

wc

The inout in the return type is then rewritten to match the inout qualifiers:

int[] ma;
const(int)[] ca;
immutable(int)[] ia;

inout(int)[] foo(inout(int)[] a) { return a; }
void test1()
{
// inout matches to mutable, so inout(int)[] is
// rewritten to int[]
int[] x = foo(ma);

// inout matches to const, so inout(int)[] is
// rewritten to const(int)[]
const(int)[] y = foo(ca);

// inout matches to immutable, so inout(int)[] is
// rewritten to immutable(int)[]
immutable(int)[] z = foo(ia);
}

inout(const(int))[] bar(inout(int)[] a) { return a; }
void test2()
{
// inout matches to mutable, so inout(const(int))[] is
// rewritten to const(int)[]
const(int)[] x = bar(ma);

// inout matches to const, so inout(const(int))[] is
// rewritten to const(int)[]
const(int)[] y = bar(ca);

// inout matches to immutable, so inout(int)[] is
// rewritten to immutable(int)[]
immutable(int)[] z = bar(ia);
}

Note: Shared types cannot be matched with inout.

Shared

Mutable data that is meant to be shared among multiple threads should be declared with the shared qualifier. This prevents unsynchronized reading and writing to the data, which would otherwise cause data races. The shared type attribute is transitive (like const and immutable).

shared int x;
shared(int)* p = &x;
//int* q = p; // error, q is not shared

For basic data types, reading and writing can normally be done with atomic operations. Use core.atomic for portability:

import core.atomic;

shared int x;

void fun()
{
//x++; // error, use atomicOp instead
x.atomicOp!"+="(1);
}

Warning: An individual read or write operation on shared data is not an error yet by default. To detect these, use the -preview=nosharedaccess compiler option. Normal initialization is allowed without an error.

import core.atomic;

int y;
shared int x = y; // OK

//x = 5; // write error with preview flag
x.atomicStore(5); // OK
//y = x; // read error with preview flag
y = x.atomicLoad(); // OK
assert(y == 5);

Casting

When working with larger types, manual synchronization can be used. To do that, shared can be cast away for the duration while mutual exclusion has been established:

struct T;
shared T* x;

void fun()
{
synchronized
{
T* p = cast(T*)x;
// operate on `*p`
}
}

An unshared reference can be cast to shared only if the source data will not be accessed for the lifetime of the cast result.

class C {}

@trusted shared(C) create()
{
auto c = new C;
// work with c without it escaping
return cast(shared)c; // OK
}

Shared Global Variables

Global (or static) shared variables are stored in common storage which is accessible across threads. Global mutable variables are stored in thread-local storage by default.

To declare global/static data to be implicitly shared across multiple threads without any compiler checks, see __gshared.

Combining Qualifiers

More than one qualifier may apply to a type. The order of application is irrelevant, for example given an unqualified type T, const shared T and shared const T are the same type. For that reason, this document depicts qualifier combinations without parentheses unless necessary and in alphabetic order.

Applying a qualifier to a type that already has that qualifier is legal but has no effect, e.g. given an unqualified type T, shared(const shared T) yields the type const shared T.

Applying the immutable qualifier to any type (qualified or not) results in immutable T. Applying any qualifier to immutable T results in immutable T. This makes immutable a fixed point of qualifier combinations and makes types such as const(immutable(shared T)) impossible to create.

alias SInt = shared int;
alias IInt = immutable int;
static assert(is(immutable(SInt) == IInt));
static assert(is(shared(IInt) == IInt));

Assuming T is an unqualified type, the graph below illustrates how qualifiers combine (combinations with immutable are omitted). For each node, applying the qualifier labeling the edge leads to the resulting type.

Implicit Qualifier Conversions

Values that have no mutable indirections (including structs that don't contain any field with mutable indirections) can be implicitly converted across mutable, const, immutable, const shared, inout and inout shared.

References to qualified objects can be implicitly converted according to the following rules:

In the graph above, any directed path is a legal implicit conversion. No other qualifier combinations than the ones shown is valid. If a directed path exists between two sets of qualifiers, the types thus qualified are called qualifier-convertible. The same information is shown below in tabular format:

Implicit Conversion of Reference Types

from/to

mutable

const

shared

inout

const shared

const inout

inout shared

const inout shared

immutable

mutable

✔

✔

const

✔

shared

✔

✔

inout

✔

✔

✔

const shared

✔

const inout

✔

✔

inout shared

✔

✔

✔

const inout shared

✔

✔

immutable

✔

✔

✔

✔

✔

Unique Expressions

If an implicit conversion is disallowed by the table, an Expression may be implicitly converted as follows:

- From mutable or shared to immutable if the expression is unique and all expressions it transitively refers to are either unique or immutable.

- From mutable to shared if the expression is unique and all expressions it transitively refers to are either unique, immutable, or shared.

- From immutable to mutable if the expression is unique.

- From shared to mutable if the expression is unique.

A Unique Expression is one for which there are no other references to the value of the expression and all expressions it transitively refers to are either also unique or are immutable. For example:

void main()
{
immutable int** p = new int*(null); // ok, unique

int x;
//immutable int** q = new int*(&x); // error, there may be other references to x

immutable int y;
immutable int** r = new immutable(int)*(&y); // ok, y is immutable
}

See also: Pure Factory Functions.

Otherwise, a CastExpression can be used to force a conversion when an implicit version is disallowed, but this cannot be done in @safe code, and the correctness of it must be verified by the user.

Functions

Function Declarations

FuncDeclaration:
StorageClassesopt BasicType FuncDeclarator FunctionBody
StorageClassesopt BasicType FuncDeclarator MissingFunctionBody
AutoFuncDeclaration

AutoFuncDeclaration:
StorageClasses Identifier FuncDeclaratorSuffix FunctionBody

FuncDeclarator:
TypeSuffixesopt Identifier FuncDeclaratorSuffix

FuncDeclaratorSuffix:
Parameters MemberFunctionAttributesopt
TemplateParameters Parameters MemberFunctionAttributesopt Constraintopt

- A FuncDeclaration with a function body is called a function definition.

- A FuncDeclaration without a function body is called a function prototype.

- A FuncDeclaration with TemplateParameters defines a function template.

Function Parameters

Parameters:
( ParameterListopt )

ParameterList:
Parameter
Parameter , ParameterListopt
VariadicArgumentsAttributesopt ...

Parameter:
ParameterDeclaration
ParameterDeclaration ...
ParameterDeclaration = AssignExpression
ParameterDeclaration = AssignExpression ...

ParameterDeclaration:
ParameterAttributesopt BasicType Declarator
ParameterAttributesopt Type

ParameterAttributes:
ParameterStorageClass
UserDefinedAttribute
ParameterAttributes ParameterStorageClass
ParameterAttributes UserDefinedAttribute

ParameterStorageClass:
auto
TypeCtor
final
in
lazy
out
ref
return
scope

VariadicArgumentsAttributes:
VariadicArgumentsAttribute
VariadicArgumentsAttribute VariadicArgumentsAttributes

VariadicArgumentsAttribute:
const
immutable
return
scope
shared

Note: In D2, declaring a parameter final is a semantic error, but not a parse error.

See also: parameter storage classes.

Function Attributes

FunctionAttributes:
FunctionAttribute
FunctionAttribute FunctionAttributes

FunctionAttribute:
FunctionAttributeKwd
Property
AtAttribute

MemberFunctionAttributes:
MemberFunctionAttribute
MemberFunctionAttribute MemberFunctionAttributes

MemberFunctionAttribute:
const
immutable
inout
return
scope
shared
FunctionAttribute

Function Bodies

FunctionBody:
SpecifiedFunctionBody
ShortenedFunctionBody

SpecifiedFunctionBody:
doopt BlockStatement
FunctionContractsopt InOutContractExpression doopt BlockStatement
FunctionContractsopt InOutStatement do BlockStatement

ShortenedFunctionBody:
InOutContractExpressionsopt => AssignExpression ;

Examples:

int hasSpecifiedBody() { return 1; }
int hasShortenedBody() => 1; // equivalent

The ShortenedFunctionBody form implies a return statement. This syntax also applies for function literals.

Function Prototypes

MissingFunctionBody:
;
FunctionContractsopt InOutContractExpression ;
FunctionContractsopt InOutStatement

Function declarations with a MissingFunctionBody, e.g.:

int foo();

that are not declared as abstract are expected to have their implementations elsewhere, and that implementation will be provided at the link step. This enables an implementation of a function to be completely hidden from the user of it, and the implementation may be in another language such as C, assembler, etc. Typically a function prototype would have non-extern(D) linkage.

A function prototype can have extern(D) linkage. This is useful for D interface files.

Function Contracts

FunctionContracts:
FunctionContract
FunctionContract FunctionContracts

FunctionContract:
InOutContractExpression
InOutStatement

InOutContractExpressions:
InOutContractExpression
InOutContractExpression InOutContractExpressions

InOutContractExpression:
InContractExpression
OutContractExpression

InOutStatement:
InStatement
OutStatement

Function Contracts specify the preconditions and postconditions of a function. They are used in Contract Programming.

Preconditions and postconditions do not affect the type of the function.

Preconditions

InContractExpression:
in ( AssertArguments )

InStatement:
in BlockStatement

An InContractExpression is a precondition.

- The first AssignExpression of the AssertArguments must evaluate to true. If it does not, the precondition has failed.

- The second AssignExpression, if present, must be implicitly convertible to type const(char)[].

An InStatement is also a precondition. Any AssertExpression appearing in an InStatement will be an InContractExpression.

Preconditions must semantically be satisfied before the function starts executing. If a precondition fails, the program enters an Invalid State.

Implementation Defined: Whether the preconditions are actually run or not is implementation defined. This is usually selectable with a compiler switch. Its behavior upon precondition failure is also usually selectable with a compiler switch. One option is to throw an AssertError with a message consisting of the optional second AssignExpression.

Best Practices: Use preconditions to validate that input arguments have values that are expected by the function.

Best Practices: Since preconditions may or may not be actually checked at runtime, avoid using preconditions that have side effects.

The expression form is:

in (expression)
in (expression, "failure string")
{
...function body...
}

The block statement form is:

in
{
...contract preconditions...
}
do
{
...function body...
}

Postconditions

OutContractExpression:
out ( ; AssertArguments )
out ( Identifier ; AssertArguments )

OutStatement:
out BlockStatement
out ( Identifier ) BlockStatement

An OutContractExpression is a postcondition.

- The first AssignExpression of the AssertArguments must evaluate to true. If it does not, the postcondition has failed.

- The second AssignExpression, if present, must be implicitly convertible to type const(char)[].

An OutStatement is also a postcondition. Any AssertExpression appearing in an OutStatement will be an OutContractExpression.

Postconditions must semantically be satisfied after the function finishes executing. If a postcondition fails, the program enters an Invalid State.

Implementation Defined: Whether the postconditions are actually run or not is implementation defined. This is usually selectable with a compiler switch. Its behavior upon postcondition failure is also usually selectable with a compiler switch. One option is to throw an AssertError with a message consisting of the optional second AssignExpression.

Best Practices: Use postconditions to validate that on leaving the function:

- Any mutable input arguments each have the expected value/range of values.

- Any return value has a correct value/range of values.

Best Practices: Since postconditions may or may not be actually checked at runtime, avoid using postconditions that have side effects.

The expression form is:

out (identifier; expression)
out (identifier; expression, "failure string")
out (; expression)
out (; expression, "failure string")
{
...function body...
}

The block statement form is:

out
{
...contract postconditions...
}
out (identifier)
{
...contract postconditions...
}
do
{
...function body...
}

The optional identifier in either type of postcondition is set to the return value of the function, and can be accessed from within the postcondition. It is implicitly const.

Example

int fun(ref int a, int b)
in (a > 0)
in (b >= 0, "b cannot be negative!")
out (r; r > 0, "return must be positive")
out (; a != 0)
{
// function body
}

int fun(ref int a, int b)
in
{
assert(a > 0);
assert(b >= 0, "b cannot be negative!");
}
out (r)
{
assert(r > 0, "return must be positive");
assert(a != 0);
}
do
{
// function body
}

The two functions are identical semantically.

In, Out and Inheritance

If a function in a derived class overrides a function from its super class, then only the preconditions of one of the function and its overridden functions must be satisfied. Overriding functions then becomes a process of loosening the preconditions.

A function without preconditions means its precondition is always satisfied. Therefore if any function in an inheritance hierarchy has no preconditions, then any preconditions on functions overriding it have no meaningful effect.

Conversely, all of the postconditions of the function and its overridden functions must to be satisfied. Adding overriding functions then becomes a processes of tightening the postconditions.

Function Return Values

One return statement per execution path is required if the function specifies a return type that is not void, unless:

- the path executes an infinite loop

- the path evaluates an assert(0) expression

- the path evaluates an expression of type noreturn

- the path contains inline assembler code

Simple forms of the first 3 cases above can be recognized by the compiler, but not all.

int error(int i)
{
if (i & 1)
i++;
else
return i;

// Error: no return statement
}

int f(int i)
{
if (i >= 0)
return i;

assert(0);
// OK, no return needed
}

Function return values not marked as ref are considered to be rvalues by the calling function. This means they cannot be passed by reference to other functions.

Pure Functions

Pure functions are annotated with the pure attribute. Pure functions cannot directly access global or static mutable state. Pure functions can only call pure functions.

Pure functions can:

- Modify the local state of the function.

- Throw exceptions.

int x;
immutable int y;

pure int foo(int i)
{
i++;     // ok, modifying local state
//x = i; // error, modifying global state
//i = x; // error, reading mutable global state
i = y;   // ok, reading immutable global state
throw new Exception("failed"); // ok
}

A pure function can override an impure function, but cannot be overridden by an impure function. I.e. it is covariant with an impure function.

Strong vs Weak Purity

A weakly pure function has parameters with mutable indirections. Program state can be modified transitively through the matching argument.

pure size_t foo(int[] arr)
{
arr[] += 1;
return arr.length;
}
int[] a = [1, 2, 3];
foo(a);
assert(a == [2, 3, 4]);

A strongly pure function has no parameters with mutable indirections and cannot modify any program state external to the function.

struct S { double x; }

pure size_t foo(immutable(int)[] arr, int num, S val)
{
//arr[num] = 1; // compile error
num = 2;        // has no side effect on the caller side
val.x = 3.14;   // ditto
return arr.length;
}

A strongly pure function can call a weakly pure function.

Special Cases

A pure function can:

- read and write the floating point exception flags

- read and write the floating point mode flags, as long as those flags are restored to their initial state upon function entry

Undefined Behavior: occurs if these flags are not restored to their initial state upon function exit. It is the programmer's responsibility to ensure this. Setting these flags is not allowed in @safe code.

Debugging

A pure function can perform impure operations in statements that are in a ConditionalStatement controlled by a DebugCondition.

Best Practices: this relaxation of purity checks in DebugConditions is intended solely to make debugging programs easier.

pure int foo(int i)
{
debug writeln("i = ", i); // ok, impure code allowed in debug statement
...
}

Nested Functions

Nested functions inside a pure function are implicitly marked as pure.

pure int foo(int x, immutable int y)
{
int bar()
// implicitly marked as pure, to be "weakly pure"
// since hidden context pointer to foo stack context is mutable
{
x = 10;     // can access states in enclosing scope
// through the mutable context pointer
return x;
}
pragma(msg, typeof(&bar));  // int delegate() pure

int baz() immutable
// qualifies hidden context pointer with immutable,
// and has no other parameters, therefore "strongly pure"
{
//return x; // error, cannot access mutable data
// through the immutable context pointer
return y;   // ok
}

// can call pure nested functions
return bar() + baz();
}

Pure Factory Functions

A pure factory function is a strongly pure function that returns a result that has only mutable indirections. All mutable memory returned by the call cannot be referenced by any other part of the program, i.e. it is newly allocated by the function. The mutable references of the result similarly cannot refer to any object that existed before the function call. This allows the result to be implicitly cast from anything to immutable or const shared, and from shared and const shared to (unshared) const. For example:

struct List { int payload; List* next; }

pure List* make(int a, int b)
{
auto result = new List(a, null);
result.next = new List(b, null);
return result;
}

void main()
{
auto list = make(1, 2);
pragma(msg, typeof(list));       // List*

immutable ilist = make(1, 2);
pragma(msg, typeof(ilist));      // immutable List*
pragma(msg, typeof(ilist.next)); // immutable List*
}

All references in make's result refer to List objects created by make, and no other part of the program refers to any of these objects. Hence the result can initialize an immutable variable.

This does not affect any Exception or Error thrown from the function.

Optimization

Implementation Defined: An implementation may assume that a strongly pure function called with arguments that have only immutable indirections (or none) that returns a result without mutable indirections will have the same effect for all invocations with equivalent arguments. It is allowed to memoize the result of the function under the assumption that equivalent arguments always produce equivalent results.

int a(int) pure; // no mutable indirections
int b(const Object) pure; // depends on argument passed
immutable(Object) c(immutable Object) pure; // always memoizable

void g();

void f(int n, const Object co, immutable Object io)
{
const int x = a(n);
g(); // `n` does not change between calls to `a`
int i = a(n); // same as `i = x`

const int y = b(co);
// `co` may have mutable indirection
g(); // may change fields of `co` through another reference
i = b(co); // call is not memoizable, result may differ

const int z = b(io);
i = b(io); // same as `i = z`
}

Such a function may still have behavior inconsistent with memoization by e.g. using casts or by changing behavior depending on the address of its parameters. An implementation is currently not required to enforce validity of memoization in all cases.

If a function throws an Exception or an Error, the assumptions related to memoization do not carry to the thrown exception.

Pure destructors do not benefit of special elision.

Nothrow Functions

Nothrow functions can only throw exceptions derived from class Error.

Nothrow functions are covariant with throwing ones.

Ref Functions

A ref function returns by reference (instead of by value). The return value of a ref function must be an lvalue (whereas the return value of a non-ref function can be an rvalue, too). An expression formed by calling a ref function is an lvalue (whereas an expression formed by calling a non-ref function is an rvalue).

int *p;

ref int foo()
{
p = new int(2);
return *p;
}

void main()
{
int i = foo();
assert(i == 2);

foo() = 3;     // reference returns can be lvalues
assert(*p == 3);
}

Returning a reference to an expired function context is not allowed. This includes local variables, temporaries and parameters that are part of an expired function context.

ref int sun()
{
int i;
return i;  // error, escaping a reference to local variable i
}

A ref parameter may not be returned by ref, unless it is return ref.

ref int moon(ref int i)
{
return i; // error
}

Auto Functions

Auto functions have their return type inferred from any ReturnStatements in the function body.

An auto function is declared without a return type. Auto functions can use any valid StorageClass, not just auto.

If there are multiple ReturnStatements, the types of them must be implicitly convertible to a common type. If there are no ReturnStatements, the return type is inferred to be void.

auto foo(int x) { return x + 3; }          // inferred to be int
pure bar(int x) { return x; return 2.5; }  // inferred to be double

Note: Return type inference also triggers attribute inference.

Auto Ref Functions

Auto ref functions can infer their return type just as auto functions do. In addition, they become ref functions if all of these apply:

- All expressions returned from the function are lvalues

- No local variables are returned

- Any parameters returned are reference parameters

- Each returned expression must implicitly convert to an lvalue of the deduced return type

auto ref f1(int x)     { return x; }  // value return
auto ref f2()          { return 3; }  // value return
auto ref f3(ref int x) { return x; }  // ref return
auto ref f4(out int x) { return x; }  // ref return
auto ref f5()
{
static int x;
return x; // ref return
}

The ref-ness of a function is determined from all ReturnStatements in the function body:

auto ref f1(ref int x) { return 3; return x; }  // ok, value return
auto ref f2(ref int x) { return x; return 3; }  // ok, value return
auto ref f3(ref int x, ref double y)
{
return x; return y;
// The return type is deduced to be double, but cast(double)x is not an lvalue,
// so f3 has a value return.
}

Auto ref functions can have an explicit return type.

auto ref int bar(ref int x) { return x; }  // ok, ref return
auto ref int foo(double x) { return x; }   // error, cannot convert double to int

Inout Functions

For extensive information see inout type qualifier.

Optional Parentheses

If a function call passes no explicit argument, i.e. it would syntactically use (), then these parentheses may be omitted, similar to a getter invocation of a property function. A UFCS call can also omit empty parentheses.

void foo() {}   // no arguments
void fun(int x = 10) {}
void bar(int[] arr) {}

void main()
{
foo();      // OK
foo;        // also OK
fun;        // OK

int[] arr;
arr.bar();  // UFCS call
arr.bar;    // also OK
}

Due to ambiguity, parentheses are required to call a delegate or a function pointer:

void main()
{
int function() fp;

assert(fp == 6);    // Error, incompatible types int function() and int
assert(*fp == 6);   // Error, incompatible types int() and int

int delegate() dg;
assert(dg == 6);    // Error, incompatible types int delegate() and int
}

If a function returns a delegate or a function pointer, any parentheses apply first to the function call, not the result. Two sets of parentheses are required to call the result directly:

int getNum() { return 6; }
int function() getFunc() { return &getNum; }

void main()
{
int function() fp;

fp = getFunc;   // implicit call
assert(fp() == 6);

fp = getFunc(); // explicit call
assert(fp() == 6);

int x = getFunc()();
assert(x == 6);
}

struct S
{
int getNum() { return 6; }
int delegate() getDel() return { return &getNum; }
}

void main()
{
S s;
int delegate() dg;

dg = s.getDel;   // implicit call
assert(dg() == 6);

dg = s.getDel(); // explicit call
assert(dg() == 6);

int y = s.getDel()();
assert(y == 6);
}

Property Functions

WARNING: The definition and usefulness of property functions is being reviewed, and the implementation is currently incomplete. Using property functions is not recommended until the definition is more certain and implementation more mature.

Properties are functions that can be syntactically treated as if they were fields or variables. Properties can be read from or written to. A property is read by calling a method or function with no arguments; a property is written by calling a method or function with its argument being the value it is set to.

Simple getter and setter properties can be written using UFCS. These can be enhanced with the additon of the @property attribute to the function, which adds the following behaviors:

- @property functions cannot be overloaded with non-@property functions with the same name.

- @property functions can only have zero, one or two parameters.

- @property functions cannot have variadic parameters.

- For the expression typeof(exp) where exp is an @property function, the type is the return type of the function, rather than the type of the function.

- For the expression __traits(compiles, exp) where exp is an @property function, a further check is made to see if the function can be called.

- @property are mangled differently, meaning that @property must be consistently used across different compilation units.

- The ObjectiveC interface recognizes @property setter functions as special and modifies them accordingly.

A simple property would be:

struct Foo
{
@property int data() { return m_data; } // read property

@property int data(int value) { return m_data = value; } // write property

private:
int m_data;
}

To use it:

int test()
{
Foo f;

f.data = 3;        // same as f.data(3);
return f.data + 3; // same as return f.data() + 3;
}

The absence of a read method means that the property is write-only. The absence of a write method means that the property is read-only. Multiple write methods can exist; the correct one is selected using the usual function overloading rules.

In all the other respects, these methods are like any other methods. They can be static, have different linkages, have their address taken, etc.

The built in properties .sizeof, .alignof, and .mangleof may not be declared as fields or methods in structs, unions, classes or enums.

If a property function has no parameters, it works as a getter. If has exactly one parameter, it works as a setter.

Virtual Functions

Virtual functions are class member functions that are called indirectly through a function pointer table, called a vtbl[], rather than directly. Member functions that are virtual can be overridden in a derived class:

class A
{
void foo(int x) {}
}

class B : A
{
override void foo(int x) {}
//override void foo() {} // error, no foo() in A
}

void test()
{
A a = new B();
a.foo(1);   // calls B.foo(int)
}

The override attribute is required when overriding a function. This is useful for catching errors when a base class's member function has its parameters changed, and all derived classes need to have their overriding functions updated.

The final method attribute prevents a subclass from overriding the method.

The following are not virtual:

- Struct and union member functions

- final member functions

- static member functions

- Member functions which are private or package

- Member template functions

Example:

class A
{
int def() { ... }
final int foo() { ... }
final private int bar() { ... }
private int abc() { ... }
}

class B : A
{
override int def() { ... }  // ok, overrides A.def
override int foo() { ... }  // error, A.foo is final
int bar() { ... }  // ok, A.bar is final private, but not virtual
int abc() { ... }  // ok, A.abc is not virtual, B.abc is virtual
}

void test()
{
A a = new B;
a.def();    // calls B.def
a.foo();    // calls A.foo
a.bar();    // calls A.bar
a.abc();    // calls A.abc
}

Member functions with Objective-C linkage are virtual even if marked with final or static, and can be overridden.

Covariance

An overriding function may be covariant with the overridden function. A covariant function has a type that is implicitly convertible to the type of the overridden function.

class A { }
class B : A { }

class Foo
{
A test() { return null; }
}

class Bar : Foo
{
// overrides and is covariant with Foo.test()
override B test() { return null; }
}

Calling Base Class Methods

To directly call a member function of a base class Base, write Base. before the function name. This avoids dynamic dispatch through a function pointer. For example:

class B
{
int foo() { return 1; }
}
class C : B
{
override int foo() { return 2; }

void test()
{
assert(B.foo() == 1);  // translated to this.B.foo(), and
// calls B.foo statically.
assert(C.foo() == 2);  // calls C.foo statically, even if
// the actual instance of 'this' is D.
}
}
class D : C
{
override int foo() { return 3; }
}
void main()
{
auto d = new D();
assert(d.foo() == 3);    // calls D.foo
assert(d.B.foo() == 1);  // calls B.foo
assert(d.C.foo() == 2);  // calls C.foo
d.test();
}

Base class methods can also be called through the super reference.

Implementation Defined: Normally calling a virtual function implies getting the address of the function at runtime by indexing into the class's vtbl[]. If the implementation can determine that the called virtual function will be statically known, such as if it is final, it can use a direct call instead.

Overload Sets and Overriding

When doing overload resolution, the functions in the base class are not considered, as they are not in the same Overload Set:

class A
{
int foo(int x) { ... }
int foo(long y) { ... }
}

class B : A
{
override int foo(long x) { ... }
}

void test()
{
B b = new B();
b.foo(1);  // calls B.foo(long), since A.foo(int) is not considered

A a = b;
a.foo(1);  // issues runtime error (instead of calling A.foo(int))
}

To include the base class's functions in the overload resolution process, use an AliasDeclaration:

class A
{
int foo(int x) { ... }
int foo(long y) { ... }
}

class B : A
{
alias foo = A.foo;
override int foo(long x) { ... }
}

void test()
{
A a = new B();
a.foo(1);      // calls A.foo(int)
B b = new B();
b.foo(1);      // calls A.foo(int)
}

If such an AliasDeclaration is not used, the derived class's functions completely override all the functions of the same name in the base class, even if the types of the parameters in the base class functions are different. It is illegal if, through implicit conversions to the base class, those other functions do get called:

class A
{
void set(long i) { }
void set(int i)  { }
}
class B : A
{
override void set(long i) { }
}

void test()
{
A a = new B;
a.set(3);   // error, use of A.set(int) is hidden by B
// use 'alias set = A.set;' to introduce base class overload set
}

Default Values

A function parameter's default value is not inherited:

class A
{
void foo(int x = 5) { ... }
}

class B : A
{
void foo(int x = 7) { ... }
}

class C : B
{
void foo(int x) { ... }
}

void test()
{
A a = new A();
a.foo();       // calls A.foo(5)

B b = new B();
b.foo();       // calls B.foo(7)

C c = new C();
c.foo();       // error, need an argument for C.foo
}

Inherited Attributes

An overriding function inherits any unspecified FunctionAttributes from the attributes of the overridden function.

class B
{
void foo() pure nothrow @safe {}
}
class D : B
{
override void foo() {}
}
void main()
{
auto d = new D();
pragma(msg, typeof(&d.foo));
// prints "void delegate() pure nothrow @safe" in compile time
}

Restrictions

The attributes @disable and deprecated are not allowed on overriding functions.

Rationale: To stop the compilation or to output the deprecation message, the implementation must be able to determine the target of the call, which can't be guaranteed when it is virtual.

class B
{
void foo() {}
}

class D : B
{
@disable override void foo() {}  // error, can't apply @disable to overriding function
}

Inline Functions

The compiler makes the decision whether to inline a function or not. This decision may be controlled by pragma(inline).

Implementation Defined: Whether a function is inlined or not is implementation defined, though any FunctionLiteral should be inlined when used in its declaration scope.

Function Overloading

Function overloading occurs when two or more functions in the same scope have the same name. The function selected is the one that is the best match to the arguments. The matching levels are:

1. No match

2. Match with implicit conversions

3. Match with qualifier conversion (if the argument type is qualifier-convertible to the parameter type)

4. Exact match

Named arguments are resolved for a candidate according to Matching Arguments to Parameters. If this fails (for example, because the overload does not have a parameter matching a named argument), the level is no match. Other than that, named arguments do not affect the matching level.

Each argument (including any this reference) is compared against the function's corresponding parameter to determine the match level for that argument. The match level for a function is the worst match level of each of its arguments.

Literals do not match ref or out parameters.

scope parameter storage class does not affect function overloading.

If two or more functions have the same match level, then partial ordering is used to disambiguate to find the best match. Partial ordering finds the most specialized function. If neither function is more specialized than the other, then it is an ambiguity error. Partial ordering is determined for functions f and g by taking the parameter types of f, constructing a list of arguments by taking the default values of those types, and attempting to match them against g. If it succeeds, then g is at least as specialized as f. For example:

class A { }
class B : A { }
class C : B { }
void foo(A);
void foo(B);

void test()
{
C c;
/* Both foo(A) and foo(B) match with implicit conversions (level 2).
* Applying partial ordering rules,
* foo(B) cannot be called with an A, and foo(A) can be called
* with a B. Therefore, foo(B) is more specialized, and is selected.
*/
foo(c); // calls foo(B)
}

A function with a variadic argument is considered less specialized than a function without.

A static member function can be overloaded with a member function. The struct, class or union of the static member function is inferred from the type of the this argument.

struct S {
void eggs(int);
static void eggs(long);
}
S s;
s.eggs(0);  // calls void eggs(int);
S.eggs(0);  // error: need `this`
s.eggs(0L); // calls static void eggs(long);
S.eggs(0L); // calls static void eggs(long);

struct T {
void bacon(int);
static void bacon(int);
}
T t;
t.bacon(0);  // error: ambiguous
T.bacon(0);  // error: ambiguous

Rationale: A static member function that doesn't need the this parameter does not need to pass it.

Overload Sets

Functions declared at the same scope overload against each other, and are called an Overload Set. An example of an overload set are functions defined at module level:

module A;
void foo() { }
void foo(long i) { }

A.foo() and A.foo(long) form an overload set. A different module can also define another overload set of functions with the same name:

module B;
class C { }
void foo(C) { }
void foo(int i) { }

and A and B can be imported by a third module, C. Both overload sets, the A.foo overload set and the B.foo overload set, are found when searching for symbol foo. An instance of foo is selected based on it matching in exactly one overload set:

import A;
import B;

void bar(C c , long i)
{
foo();    // calls A.foo()
foo(i);  // calls A.foo(long)
foo(c);   // calls B.foo(C)
foo(1,2); // error, does not match any foo
foo(1);   // error, matches A.foo(long) and B.foo(int)
A.foo(1); // calls A.foo(long)
}

Even though B.foo(int) is a better match than A.foo(long) for foo(1), it is an error because the two matches are in different overload sets.

Overload sets can be merged with an alias declaration:

import A;
import B;

alias foo = A.foo;
alias foo = B.foo;

void bar(C c)
{
foo();    // calls A.foo()
foo(1L);  // calls A.foo(long)
foo(c);   // calls B.foo(C)
foo(1,2); // error, does not match any foo
foo(1);   // calls B.foo(int)
A.foo(1); // calls A.foo(long)
}

Function Parameters

Parameter Storage Classes

Parameter storage classes are in, out, ref, lazy, return and scope. Parameters can also take the type constructors const, immutable, shared and inout.

in, out, ref and lazy are mutually exclusive. The first three are used to denote input, output and input/output parameters, respectively. For example:

int read(in char[] input, ref size_t count, out int errno);

void main()
{
size_t a = 42;
int b;
int r = read("Hello World", a, b);
}

read has three parameters. input will only be read and no reference to it will be retained. count may be read and written to, and errno will be set to a value from within the function.

The argument "Hello World" gets bound to parameter input, a gets bound to count and b to errno.

Parameter Storage Class and Type Constructor Overview

Storage Class

Description

none

The parameter will be a mutable copy of its argument.

in

The parameter is an input to the function.

out

The argument must be an lvalue, which will be passed by reference and initialized upon function entry with the default value (T.init) of its type.

ref

The parameter is an input/output parameter, passed by reference.

scope

The parameter must not escape the function call (e.g. by being assigned to a global variable). Ignored for any parameter that is not a reference type.

return

Parameter may be returned or copied to the first parameter, but otherwise does not escape from the function. Such copies are required not to outlive the argument(s) they were derived from. Ignored for parameters with no references. See Scope Parameters.

lazy

argument is evaluated by the called function and not by the caller

Type Constructor

Description

const

argument is implicitly converted to a const type

immutable

argument is implicitly converted to an immutable type

shared

argument is implicitly converted to a shared type

inout

argument is implicitly converted to an inout type

In Parameters

The parameter is an input to the function. Input parameters behave as if they have the const storage class.

Note: The following requires the -preview=in switch and a compiler compliant with dmd v2.094.0 or higher.

in parameters also behave like scope parameters.

const(int[]) g(in int[] a) @safe
{
a[0]++; // Error: cannot modify const expression
return a; // Error: scope parameter `a` may not be returned
}

Input parameters may also be passed by reference by the compiler. Unlike ref parameters, in parameters can bind to both lvalues and rvalues (such as literals).

- Types that would trigger a side effect if passed by value (such as types with a copy constructor, postblit, or destructor) will always be passed by reference.

- Types which cannot be copied (e.g. if their copy constructor is marked as @disable) will always be passed by reference.

- Dynamic arrays, classes, associative arrays, function pointers, and delegates will always be passed by value.

Implementation Defined: If the type of the parameter does not fall in one of those categories, whether or not it is passed by reference is implementation defined, and the backend is free to choose the method that will best fit the ABI of the platform.

Ref and Out Parameters

By default, parameters take rvalue arguments. A ref parameter takes an lvalue argument, so changes to its value will operate on the caller's argument.

void inc(ref int x)
{
x += 1;
}

void seattle()
{
int z = 3;
inc(z);
assert(z == 4);
}

A ref parameter can also be returned by reference, see Return Ref Parameters.

An out parameter is similar to a ref parameter, except it is initialized by default construction upon function invocation.

void zero(out int x)
{
assert(x == 0);
}

void two(out int x)
{
x = 2;
}

void tacoma()
{
int a = 3;
zero(a);
assert(a == 0);

int y = 3;
two(y);
assert(y == 2);
}

For dynamic array and class object parameters, which are always passed by reference, out and ref apply only to the reference and not the contents.

Lazy Parameters

An argument to a lazy parameter is not evaluated before the function is called. The argument is only evaluated if/when the parameter is evaluated within the function. Hence, a lazy argument can be executed 0 or more times.

import std.stdio : writeln;

void main()
{
int x;
3.times(writeln(x++));
writeln("-");
writeln(x);
}

void times(int n, lazy void exp)
{
while (n--)
exp();
}

prints to the console:

0
1
2
−
3

A lazy parameter cannot be an lvalue.

The underlying delegate of the lazy parameter may be extracted by using the & operator:

void test(lazy int dg)
{
int delegate() dg_ = &dg;
assert(dg_() == 7);
assert(dg == dg_());
}

void main()
{
int a = 7;
test(a);
}

A lazy parameter of type void can accept an argument of any type.

See Also: Lazy Variadic Functions

Default Arguments

Function parameter declarations can have default values:

void foo(int x, int y = 3)
{
...
}
...
foo(4);   // same as foo(4, 3);

Default parameters are resolved and semantically checked in the context of the function declaration.

module m;
private immutable int b;
pure void g(int a = b) {}

import m;
int b;
pure void f()
{
g();  // ok, uses m.b
}

The attributes of the AssignExpression are applied where the default expression is used.

module m;
int b;
pure void g(int a = b) {}

import m;
enum int b = 3;
pure void f()
{
g();  // error, cannot access mutable global `m.b` in pure function
}

See also: function type aliases with default values.

Return Ref Parameters

Return ref parameters are used with ref functions to ensure that the returned reference will not outlive the matching argument's lifetime.

ref int identity(return ref int x) {
return x; // pass-through function that does nothing
}

ref int fun() {
int x;
return identity(x); // Error: escaping reference to local variable x
}

ref int gun(return ref int x) {
return identity(x); // OK
}

Returning the address of a ref variable is also checked in @safe code.

int* pluto(ref int i) @safe
{
return &i; // error: returning &i escapes a reference to parameter i
}

int* mars(return ref int i) @safe
{
return &i;  // OK with -preview=dip1000
}

If a function returns void, and the first parameter is ref or out, then all subsequent return ref parameters are considered as being assigned to the first parameter for lifetime checking.

void f(ref scope int* p, return ref int i) @safe
{
p = &i; // OK with -preview=dip1000
}

void main() @safe
{
int i;
int* p;
f(p, i); // OK, lifetime of p is shorter than i
*p = 5;
assert(i == 5);

int j;
//f(p, j); // error, lifetime of p is longer than j
}

The this reference parameter to a struct non-static member function is considered the first parameter.

struct S
{
private int* p;

void f(return ref int i) scope @safe
{
p = &i; // OK with -preview=dip1000
}
}

void main() @safe
{
int i;
S s;
s.f(i); // OK, lifetime of `s` is shorter than `i`
*s.p = 2;
assert(i == 2);
}

If there are multiple return ref parameters, the lifetime of the return value is the smallest lifetime of the corresponding arguments.

Neither the type of the return ref parameter(s) nor the type of the return value is considered when determining the lifetime of the return value.

It is not an error if the return type does not contain any indirections.

int mercury(return ref int i)
{
return i; // ok
}

Template functions, auto functions, nested functions and lambdas can deduce the return attribute.

@safe:

ref int templateFunction()(ref int i)
{
return i; // ok
}

ref auto autoFunction(ref int i)
{
return i; // ok
}

void uranus()
{
ref int nestedFunction(ref int i)
{
return i; // ok
}
auto lambdaFunction =
(ref int i)
{
return &i; // ok
};
}

Struct Return Methods

Struct non-static methods can be marked with the return attribute to ensure a returned reference will not outlive the struct instance.

struct S
{
private int x;
ref int get() return { return x; }
}

ref int escape()
{
S s;
return s.get(); // Error: escaping reference to local variable s
}

The hidden this ref-parameter then becomes return ref.

The return attribute can also be used to limit the lifetime of the returned value, even when the method is not ref:

struct S
{
private int i;
int* get() return @safe => &i;
}

void f() @safe
{
int* p;
{
S s;
int *q = s.get(); // OK, q has shorter lifetime than s
p = s.get(); // error, p has longer lifetime
p = (new S).get(); // OK, heap allocated S
}
}

Scope Parameters

A scope parameter of reference type must not escape the function call (e.g. by being assigned to a global variable). It has no effect for non-reference types. scope escape analysis is only done for @safe functions. For other functions scope semantics must be manually enforced.

Note: @safe escape analysis is only done with the -preview=dip1000 switch.

@safe:

int* gp;
void thorin(scope int*);
void gloin(int*);
int* balin(scope int* q, int* r)
{
gp = q; // error, q escapes to global gp
gp = r; // ok

thorin(q); // ok, q does not escape thorin()
thorin(r); // ok

gloin(q); // error, gloin() escapes q
gloin(r); // ok that gloin() escapes r

return q; // error, cannot return 'scope' q
return r; // ok
}

Implementation Defined: As a scope parameter must not escape, the compiler can potentially avoid heap-allocating a unique argument to a scope parameter. Due to this, passing an array literal, delegate literal or a NewExpression to a scope parameter may be allowed in a @nogc context.

Return Scope Parameters

Parameters marked as return scope that contain indirections can only escape those indirections via the function's return value.

@safe:

int* gp;
void thorin(scope int*);
void gloin(int*);
int* balin(return scope int* p)
{
gp = p; // error, p escapes to global gp
thorin(p); // ok, p does not escape thorin()
gloin(p); // error, gloin() escapes p
return p; // ok
}

Class references are considered pointers that are subject to scope.

@safe:

class C { }
C gp;
void thorin(scope C);
void gloin(C);
C balin(return scope C p, scope C q, C r)
{
gp = p; // error, p escapes to global gp
gp = q; // error, q escapes to global gp
gp = r; // ok

thorin(p); // ok, p does not escape thorin()
thorin(q); // ok
thorin(r); // ok

gloin(p); // error, gloin() escapes p
gloin(q); // error, gloin() escapes q
gloin(r); // ok that gloin() escapes r

return p; // ok
return q; // error, cannot return 'scope' q
return r; // ok
}

return scope can be applied to the this of class and interface member functions.

class C
{
C bofur() return scope { return this; }
}

Template functions, auto functions, nested functions and lambdas can deduce the return scope attribute.

Ref Return Scope Parameters

It is not possible to have both return ref and return scope semantics for the same parameter. When a parameter is passed by ref and has both the return and scope storage classes, it gets return scope semantics if and only if the return and scope keywords appear adjacent to each other, in that order. Specifying a return ref and scope parameter enables returning a reference to a scope pointer. In all other cases, the parameter has return ref semantics and regular scope semantics.

U xerxes(       ref return scope V v) // (1) ref and return scope
U sargon(return ref        scope V v) // (2) return ref and scope

struct S
{
// note: in struct member functions, the implicit `this` parameter
// is passed by `ref`

U xerxes() return scope;        // return scope
U sargon()        scope return; // return ref, `return` comes after `scope`
U xerxes() return const scope;  // return ref, `return` and `scope` are not adjacent
}

Example of combinations of return scope, return ref, and scope semantics:

@safe:

int* globalPtr;

struct S
{
int  val;
int* ptr;

this(return scope ref int* p) { ptr = p; }

// note: `this` is passed by `ref` in structs

int* retRefA() scope return // return-ref, scope
{
globalPtr = this.ptr; // disallowed, `this` is `scope`
return &this.val; // allowed, `return` means `return ref`
}

ref int retRefB() scope return // return-ref, scope
{
globalPtr = this.ptr; // disallowed, `this` is `scope`
return  this.val; // allowed, `return` means `return ref`
}

int* retScopeA() return scope // ref, return-scope
{
return &this.val; // disallowed, escaping a reference to `this`
return this.ptr;  // allowed, returning a `return scope` pointer
}

ref int retScopeB() return scope // ref, return-scope
{
return this.val;  // disallowed, escaping a reference to `this`
return *this.ptr; // allowed, returning a `return scope` pointer
}

ref int* retRefScopeC() scope return // return-ref, scope
{
return this.ptr; // allowed, returning a reference to a scope pointer
}
}

int* retRefA(return ref scope S s)
{
globalPtr = s.ptr; // disallowed, `s` is `scope`
return &s.val; // allowed, returning a reference to `return ref s`
}

ref int retRefB(return ref scope S s)
{
globalPtr = s.ptr; // disallowed, `s` is `scope`
return s.val;
}

int* retScopeA(ref return scope S s)
{
return &s.val; // disallowed, escaping a reference to `s`
return s.ptr;  // allowed, returning a `return scope` pointer
}

ref int retScopeB(ref return scope S s)
{
return s.val;  // disallowed, escaping a reference to `s`
return *s.ptr; // allowed, returning a `return scope` pointer
}

ref int* retRefScopeC(return ref scope int* p)
{
return p; // allowed, returning a reference to a scope pointer
}

Inferred scope parameters in pure functions
When a parameter is not marked or inferred scope, it may still be @safe to assign it a scope pointer in a function call. The following conditions need to be met:

- The function is pure, hence the argument cannot be assigned to a global variable

- The function is nothrow, hence the argument cannot be assigned to a thrown Exception object

- None of the other parameters have mutable indirections, hence the argument cannot be assigned to a longer-lived variable

Then, the parameter is still treated as scope or return scope depending on the return type of the function:

- If the function returns by ref or has a return type that contains pointers, the argument could be returned, so it is treated as return scope

- Otherwise, the argument cannot escape the function, so it is treated as scope

@safe:

int dereference(int* x) pure nothrow;
int* identity(int* x) pure nothrow;
int* identityThrow(int* x) pure;
void assignToRef(int* x, ref int* escapeHatch) pure nothrow;
void assignToPtr(int* x, int** escapeHatch) pure nothrow;
void cannotAssignTo(int* x, const ref int* noEscapeHatch) pure nothrow;

int* globalPtr;

int* test(scope int* ptr)
{
int result = dereference(ptr); // allowed, treated as `scope`
int* ptr2 = identity(ptr); // allowed, treated as `return scope`
int* ptr3 = identityThrow(ptr); // not allowed, can throw an `Exception`
assignToRef(ptr, globalPtr); // not allowed, mutable second parameter
assignToPtr(ptr, &globalPtr); // not allowed, mutable second parameter
cannotAssignTo(ptr, globalPtr); // allowed

return ptr2; // not allowed, ptr2 is inferred `scope` now
}

User-Defined Attributes for Parameters
See also: User-Defined Attributes
Variadic Functions

Variadic Functions take a variable number of arguments. There are three forms:

1. C-style variadic functions

2. Variadic functions with type info

3. Typesafe variadic functions

C-style Variadic Functions

A C-style variadic function is declared with a parameter ... as the last function parameter. It has non-D linkage, such as extern (C).

extern (C) void dry(int x, int y, ...); // C-style Variadic Function

void spin()
{
dry(3, 4);      // ok, no variadic arguments
dry(3, 4, 6.8); // ok, one variadic argument
//dry(2);       // error, no argument for parameter y
}

There must be at least one non-variadic parameter declared.

extern (C) int def(...); // error, must have at least one parameter

C-style variadic functions match the C calling convention for variadic functions, and can call C Standard library functions like printf.

extern (C) int printf(const(char)*, ...);

void main()
{
printf("hello world\n");
}

C-style variadic functions cannot be marked as @safe.

To access the variadic arguments, import the standard library module core.stdc.stdarg.

import core.stdc.stdarg;
import std.stdio;

extern (C) void rinse(int x, int y, ...)
{
va_list args;
va_start(args, y); // y is the last named parameter
int z;
va_arg(args, z);   // z is set to 5
va_end(args);
writeln(z);
}

void main()
{
rinse(3, 4, 5);   // first variadic argument is 5
}

D-style Variadic Functions

D-style variadic functions have D linkage and ... as the last parameter.

... can be the only parameter.

If there are parameters preceding the ... parameter, there must be a comma separating them from the ....

Note: If the comma is omitted, it is a Typesafe Variadic Function.

int abc(char c, ...);   // one required parameter: c
int def(...);           // no required parameters
int ghi(int i ...);     // a typesafe variadic function
//int boo(, ...);       // error

Two hidden arguments are passed to the function:

- _argptr

- TypeInfo[] _arguments

_argptr is a pointer to the first of the variadic arguments. To access the variadic arguments, import core.vararg. Use _argptr in conjunction with va_arg:

import core.vararg;
import std.stdio;

@system void foo(int x, int y, ...)
{
int z = va_arg!int(_argptr); // z is set to 5 and _argptr is advanced
// to the next argument
writeln(z); // 5
}

void main()
{
foo(3, 4, 5);   // first variadic argument is 5
}

_arguments gives the number of arguments and the typeid of each, enabling type safety to be checked at run time.

import std.stdio;

void main()
{
Foo f = new Foo();
Bar b = new Bar();

writefln("%s", f);
printargs(1, 2, 3L, 4.5, f, b);
}

class Foo { int x = 3; }
class Bar { long y = 4; }

import core.vararg;

@system void printargs(int x, ...)
{
writefln("%d arguments", _arguments.length);
for (int i = 0; i < _arguments.length; i++)
{
writeln(_arguments[i]);

if (_arguments[i] == typeid(int))
{
int j = va_arg!(int)(_argptr);
writefln("\t%d", j);
}
else if (_arguments[i] == typeid(long))
{
long j = va_arg!(long)(_argptr);
writefln("\t%d", j);
}
else if (_arguments[i] == typeid(double))
{
double d = va_arg!(double)(_argptr);
writefln("\t%g", d);
}
else if (_arguments[i] == typeid(Foo))
{
Foo f = va_arg!(Foo)(_argptr);
writefln("\t%d", f.x);
}
else if (_arguments[i] == typeid(Bar))
{
Bar b = va_arg!(Bar)(_argptr);
writefln("\t%d", b.y);
}
else
assert(0);
}
}

which prints:

0x00870FE0
5 arguments
int
2
long
3
double
4.5
Foo
3
Bar
4

D-style variadic functions cannot be marked as @safe (if they call va_arg).

Typesafe Variadic Functions

A typesafe variadic function has D linkage and a variadic parameter, which is typically an array followed by .... The variadic parameter must be the last one. When passing component arguments (e.g. array elements), the variadic parameter is implicitly constructed from the given arguments.

A dynamic array variadic parameter accepts either a dynamic array argument or any number of arguments, each of which must implicitly convert to the array element type:

int sum(int[] ar ...) // typesafe variadic function
{
int s;
foreach (int x; ar)
s += x;
return s;
}

void main()
{
assert(sum(1, 2, 3) == 6);
assert(sum() == 0);

int[3] ii = [4, 5, 6];
assert(sum(ii) == 15);
}

A static array variadic parameter accepts either a static array argument or a fixed number of arguments which matches the array dimension:

int sum(int[3] ar ...) // typesafe variadic function
{
int s;
foreach (int x; ar)
s += x;
return s;
}

void main()
{
int i;
//i = sum(2, 3);    // error, need 3 values for array
i = sum(1, 2, 3);
assert(i == 6);

int[3] ii = [4, 5, 6];
i = sum(ii);
assert(i == 15);

int[] jj = ii;
//i = sum(jj); // error, type mismatch
}

Deprecated:
For class objects:

int tesla(int x, C c ...)
{
return x + c.x;
}

class C
{
int x;
string s;

this(int x, string s)
{
this.x = x;
this.s = s;
}
}

void edison()
{
C g = new C(3, "abc");
tesla(1, c);         // ok, since c is an instance of C
tesla(1, 4, "def");  // ok
tesla(1, 5);         // error, no matching constructor for C
}

The lifetime of the variadic class object or array instance ends at the end of the function.

C orville(C c ...)
{
return c;   // error, c instance contents invalid after return
}

int[] wilbur(int[] a ...)
{
return a;       // error, array contents invalid after return
return a[0..1]; // error, array contents invalid after return
return a.dup;   // ok, since copy is made
}

Implementation Defined: the variadic object or array instance may be constructed on the stack.

For other types, the variadic parameter matches exactly one argument, which is passed by value.

int neil(int i ...)
{
return i;
}

void buzz()
{
neil();     // error, missing argument
neil(3);    // returns 3
neil(3, 4); // error, too many arguments
int[] x;
neil(x);    // error, type mismatch
}

Lazy Variadic Functions

If the variadic parameter of a function is an array of delegates with no parameters, then each of the arguments whose type does not match that of the delegate is converted to a delegate of that type.

void hal(scope int delegate()[] dgs ...);

void dave()
{
int delegate() dg;
hal(1, 3+x, dg, cast(int delegate())null);   // (1)
hal( { return 1; }, { return 3+x; }, dg, null ); // same as (1)
}

The variadic delegate array differs from using a lazy variadic array. With the latter each array element access would evaluate every array element. With the former, only the element being accessed would be evaluated.

import std.stdio;

void main()
{
int x;
ming(++x, ++x);

int y;
flash(++y, ++y);
}

// lazy variadic array
void ming(lazy int[] arr...)
{
writeln(arr[0]); // 1
writeln(arr[1]); // 4
}

// variadic delegate array
void flash(scope int delegate()[] arr ...)
{
writeln(arr[0]()); // 1
writeln(arr[1]()); // 2
}

Best Practices: Use scope when declaring the array of delegates parameter. This will prevent a closure being generated for the delegate, as scope means the delegate will not escape the function.

Hidden Parameters

- Non-static member functions all have a hidden parameter called the this reference, which refers to the object for which the function is called.

- D-style variadic functions have hidden parameters.

- Functions with Objective-C linkage have an additional hidden, unnamed, parameter which is the selector it was called with.

Ref Scope Return Cases

Definitions

Definitions

Term

Description

I

type that contains no indirections

P

type that contains indirections

X

type that may or may not contain indirections

p

parameter of type P

i

parameter of type I

ref

ref or out parameter

returned

returned via the return statement

escaped

stored in a global or other memory not in the function’s stack frame

Classification

A parameter must be in one of the following states:

Classification

Term

Description

None

p may be returned or escaped

ReturnScope

p may be returned but not escaped

Scope

p may be neither returned nor escaped

Ref

p may be returned or escaped, ref may not be returned nor escaped

ReturnRef

p may be returned or escaped, ref may be returned but not escaped

RefScope

p may be neither returned nor escaped, ref may not be returned nor escaped

ReturnRef-Scope

p may be neither returned nor escaped, ref may be returned but not escaped

Ref-ReturnScope

p may be returned but not escaped, ref may not be returned nor escaped

ReturnRef-ReturnScope

p may be returned but not escaped, ref may be returned but not escaped. This isn't expressible with the current syntax and so is not allowed.

Mapping Syntax Onto Classification

The juxtaposition of return immediately preceding scope means ReturnScope. Otherwise, return and ref in any position means ReturnRef.

Mapping

Example

Classification

Comments

X foo(P p)

None

X foo(scope P p)

Scope

P foo(return scope P p)

ReturnScope

I foo(return scope P p)

Scope

The return is dropped because the return type I contains no pointers.

P foo(return P p)

ReturnScope

Makes no sense to have return without scope.

I foo(return P p)

Scope

The return is dropped because the return type I contains no pointers.

X foo(ref P p)

Ref

X foo(ref scope P p)

RefScope

P foo(ref return scope P p)

Ref-ReturnScope

P foo(return ref scope P p)

ReturnRef-Scope

I foo(ref return scope P p)

RefScope

P foo(ref return P p)

ReturnRef

I foo(ref return P p)

Ref

ref X foo(P p)

None

ref X foo(scope P p)

Scope

ref X foo(return scope P p)

ReturnScope

ref X foo(return P p)

ReturnScope

Makes no sense to have return without scope.

ref X foo(ref P p)

Ref

ref X foo(ref scope P p)

RefScope

ref X foo(ref return scope P p)

Ref-ReturnScope

ref X foo(return ref scope P p)

ReturnRef-Scope

ref X foo(ref return P p)

ReturnRef

X foo(I i)

None

X foo(scope I i)

None

X foo(return scope I i)

None

X foo(return I i)

None

X foo(ref I i)

Ref

X foo(ref scope I i)

Ref

X foo(ref return scope I i)

Ref

P foo(ref return I i)

ReturnRef

I foo(ref return I i)

Ref

ref X foo(I i)

None

ref X foo(scope I i)

None

ref X foo(return scope I i)

None

ref X foo(return I i)

None

ref X foo(ref I i)

Ref

ref X foo(ref scope I i)

Ref

ref X foo(ref return scope I i)

ReturnRef

ref X foo(ref return I i)

ReturnRef

Member Functions

Member functions are rewritten as if the this parameter is the first parameter of a non-member function,

struct S {
X foo();
}

is treated as:

X foo(ref S);

and:

class C {
X foo()
}

is treated as:

X foo(P)

P and ref

The rules account for switching between ref and P, such as:

int* foo(return ref int i) { return &i; }
ref int foo(int* p) { return *p; }

Covariance

Covariance means a parameter with restrictions can be converted to a parameter with fewer restrictions. This is deducible from the description of each state.

Note: ref is not covariant with non-ref, so those entries are omitted from the table for simplicity.

Covariance

From\To

None

ReturnScope

Scope

None

✔

ReturnScope

✔

✔

Scope

✔

✔

✔

Ref Covariance

From\To

Ref

ReturnRef

RefScope

ReturnRef-Scope

Ref-ReturnScope

Ref

✔

✔

ReturnRef

✔

RefScope

✔

✔

✔

✔

✔

ReturnRef-Scope

✔

✔

Ref-ReturnScope

✔

✔

✔

For example, scope matches all non-ref parameters, and ref scope matches all ref parameters.

Local Variables

Local variables are declared within the scope of a function. Function parameters are included.

A local variable cannot be read without first assigning it a value.

Implementation Defined: The implementation may not always be able to detect these cases.

The address of or reference to a local non-static variable cannot be returned from the function.

A local variable and a label in the same function cannot have the same name.

A local variable cannot hide another local variable in the same function.

Rationale: whenever this is done it often is a bug or at least looks like a bug.

ref double func(int x)
{
int x;       // error, hides previous definition of x
double y;
{
char y;  // error, hides previous definition of y
int z;
}
{
wchar z; // Ok, previous z is out of scope
}
z:             // error, z is a local variable and a label
return y;    // error, returning ref to local
}

Local Static Variables

Local variables in functions declared as static, shared static or __gshared are statically allocated rather than being allocated on the stack. The lifetime of __gshared and shared static variables begins when the function is first executed and ends when the program ends. The lifetime of static variables begins when the function is first executed within the thread and ends when that thread terminates.

import std.stdio : writeln;

void foo()
{
static int n;
if (++n == 100)
writeln("called 100 times");
}

The initializer for a static variable must be evaluatable at compile time. There are no static constructors or static destructors for static local variables.

Although static variable name visibility follows the usual scoping rules, the names of them must be unique within a particular function.

void main()
{
{ static int x; }
{ static int x; } // error
{ int i; }
{ int i; } // ok
}

Nested Functions

Functions may be nested within other functions:

int bar(int a)
{
int foo(int b)
{
int abc() { return 1; }

return b + abc();
}
return foo(a);
}

void test()
{
int i = bar(3); // i is assigned 4
}

Nested functions can be accessed only if the name is in scope.

void foo()
{
void A()
{
B(); // error, B() is forward referenced
C(); // error, C undefined
}
void B()
{
A(); // ok, in scope
void C()
{
void D()
{
A();      // ok
B();      // ok
C();      // ok
D();      // ok
}
}
}
A(); // ok
B(); // ok
C(); // error, C undefined
}

and:

int bar(int a)
{
int foo(int b) { return b + 1; }
int abc(int b) { return foo(b); }   // ok
return foo(a);
}

void test()
{
int i = bar(3);     // ok
int j = bar.foo(3); // error, bar.foo not visible
}

Nested functions have access to the variables and other symbols defined by the lexically enclosing function. This access includes both the ability to read and write them.

int bar(int a)
{
int c = 3;

int foo(int b)
{
b += c;       // 4 is added to b
c++;          // bar.c is now 5
return b + c; // 12 is returned
}
c = 4;
int i = foo(a); // i is set to 12
return i + c;   // returns 17
}

void test()
{
int i = bar(3); // i is assigned 17
}

This access can span multiple nesting levels:

int bar(int a)
{
int c = 3;

int foo(int b)
{
int abc()
{
return c;   // access bar.c
}
return b + c + abc();
}
return foo(3);
}

Static nested functions cannot access any stack variables of any lexically enclosing function, but can access static variables. This is analogous to how static member functions behave.

int bar(int a)
{
int c;
static int d;

static int foo(int b)
{
b = d;          // ok
b = c;          // error, foo() cannot access frame of bar()
return b + 1;
}
return foo(a);
}

Functions can be nested within member functions:

struct Foo
{
int a;

int bar()
{
int c;

int foo()
{
return c + a;
}
return 0;
}
}

Nested functions always have the D function linkage type.

Declaration Order

Unlike module level declarations, declarations within function scope are processed in order. This means that two nested functions cannot mutually call each other:

void test()
{
void foo() { bar(); } // error, bar not defined
void bar() { foo(); } // ok
}

There are several workarounds for this limitation:

- Declare the functions to be static members of a nested struct:

void test()
{
static struct S
{
static void foo() { bar(); } // ok
static void bar() { foo(); } // ok
}

S.foo();  // compiles (but note the infinite runtime loop)
}

- Declare one or more of the functions to be function templates even if they take no specific template arguments:

void test()
{
void foo()() { bar(); } // ok (foo is a function template)
void bar()   { foo(); } // ok
}

- Declare the functions inside of a mixin template:

mixin template T()
{
void foo() { bar(); } // ok
void bar() { foo(); } // ok
}

void main()
{
mixin T!();
}

- Use a delegate:

void test()
{
void delegate() fp;
void foo() { fp(); }
void bar() { foo(); }
fp = &bar;
}

Nested functions cannot be overloaded.

Function Pointers, Delegates and Closures

Function Pointers

A function pointer is declared with the function keyword:

void f(int);
void function(int) fp = &f; // fp is a pointer to a function taking an int

A function pointer can point to a static nested function:

int function() fp;  // fp is a pointer to a function returning an int

void test()
{
static int a = 7;
static int foo() { return a + 3; }

fp = &foo;
}

void main()
{
assert(!fp);
test();
int i = fp();
assert(i == 10);
}

Implementation Defined: Two functions with identical bodies, or two functions that compile to identical assembly code, are not guaranteed to have distinct function pointer values. The implementation may merge functions bodies into one if they compile to identical code.

int abc(int x)   { return x + 1; }
uint def(uint y) { return y + 1; }

int function(int)   fp1 = &abc;
uint function(uint) fp2 = &def;
// Do not rely on fp1 and fp2 being different values; the compiler may merge
// them.

Delegates & Closures

A delegate can be set to a non-static nested function:

int delegate() dg;

void test()
{
int a = 7;
int foo() { return a + 3; }

dg = &foo;
int i = dg(); // i is set to 10
}

void main()
{
test();
int i = dg(); // ok, test.a is in a closure and still exists
assert(i == 10);
}

The stack variables referenced by a nested function are still valid even after the function exits (NOTE this is different from D 1.0). This combining of the environment and the function is called a dynamic closure.

Those referenced stack variables that make up the closure are allocated on the GC heap, unless:

- The closure is passed to a scope parameter.

- The closure is an initializer for a scope variable.

- The closure is assigned to a scope variable.

@nogc:
void f(scope int delegate());
void g(int delegate());

void main()
{
int i;
int h() { return i; }
h(); // OK
scope x = &h; // OK
x(); // OK
//auto y = &h; // error, can't allocate closure in @nogc function
f(&h); // OK
//g(&h); // error

// delegate literals
f(() => i); // OK
scope d = () => i; // OK
d = () => i + 1; // OK
f(d);
//g(() => i); // error, can't allocate closure in @nogc function
}

Note: Returning addresses of stack variables, however, is not a closure and is an error.

Method Delegates

Delegates to non-static nested functions contain two pieces of data: the pointer to the stack frame of the lexically enclosing function (called the context pointer) and the address of the function. This is analogous to struct/class non-static member function delegates consisting of a this pointer and the address of the member function. Both forms of delegates are indistinguishable, and are the same type.

A delegate can be set to a particular object and method using &obj.method:

struct Foo
{
int a;
int get() { return a; }
}

int add1(int delegate() dg)
{
return dg() + 1;
}

void main()
{
Foo f = {7};
int delegate() dg = &f.get; // bind to an instance of Foo and a method
int i = add1(dg);
assert(i == 8);

int x = 27;
int abc() { return x; }

i = add1(&abc);
assert(i == 28);
}

Initialization

Function pointers are zero-initialized by default. They can be initialized to the address of any function (including a function literal). Initialization with the address of a function that requires a context pointer is not allowed in @safe functions.

Undefined Behavior: Calling a function pointer that was set to point to a function that requires a context pointer.

struct S
{
static int sfunc();
int member();   // has hidden `this` reference parameter
}

@safe void sun()
{
int function() fp = &S.sfunc;
fp(); // Ok
fp = &S.member; // error
}

@system void moon()
{
int function() fp = &S.member; // Ok because @system
fp(); // undefined behavior
}

Delegates are zero-initialized by default. They can be initialized by taking the address of a non-static member function, but a context pointer must be supplied. They can be initialized by taking the address of a non-static nested function or function literal, where the context pointer will be set to point to the stack frame, closure, or null.

Delegates cannot be initialized by taking the address of a global function, a static member function, or a static nested function.

struct S
{
static int sfunc();
int member() { return 1; }
}

void main()
{
S s;
int delegate() dg = &s.member; // Ok, s supplies context pointer
assert(dg() == 1);

//dg = &S.sfunc;  // error
//dg = &S.member; // error

int moon() { return 2; }
dg = &moon;     // Ok
assert(dg() == 2);

static int mars() { return 3; }
//dg = &mars;     // error

dg = () { return 4; }; // Ok
assert(dg() == 4);
}

The last assignment uses a FunctionLiteral, which is inferred as a delegate.

Note: Function pointers can be passed to functions taking a delegate argument by passing them through the std.functional.toDelegate template, which converts any callable to a delegate with a null context pointer.

Anonymous Functions and Anonymous Delegates

See FunctionLiterals.

main() Function

For console programs, main() serves as the entry point. It gets called after all the module initializers are run, and after any unittests are run. After it returns, all the module destructors are run. main() must be declared as follows:

MainFunction:
MainReturnDecl main() FunctionBody
MainReturnDecl main(string[] Identifier) FunctionBody

MainReturnDecl:
void
int
noreturn
auto

- If main returns void, the OS will receive a zero value on success.

- If main returns void or noreturn, the OS will receive a non-zero value on abnormal termination, such as an uncaught exception.

- If main is declared as auto, the inferred return type must be one of void, int and noreturn.

If the string[] parameter is declared, the parameter will hold arguments passed to the program by the OS. The first argument is typically the executable name, followed by any command-line arguments.

Note: The runtime can remove any arguments prefixed --DRT-.

Note: The aforementioned return / parameter types may be annotated with const, immutable. They may also be replaced by enum's with matching base types.

The main function must have D linkage.

Attributes may be added as needed, e.g. @safe, @nogc, nothrow, etc.

extern(C) main() Function

Programs may define an extern(C) main function as an alternative to the standard entry point. This form is required for BetterC programs.

A C main function must be declared as follows:

CMainFunction:
extern (C) MainReturnDecl main(CmainParametersopt) BlockStatement

CmainParameters:
int Identifier, char** Identifier
int Identifier, char** Identifier, char** Identifier

When defined, the first two parameters denote a C-style array (length + pointer) that holds the arguments passed to the program by the OS. The third parameter is a POSIX extension called environ and holds information about the current environment variables.

Note: The exemption for storage classes / enum's defined for a D main function also applies to C main functions.

This function takes the place of the C main function and is executed immediately without any setup or teardown associated with a D main function. Programs reliant on module constructors, module destructors, or unittests need to manually perform (de)initialization using the appropriate runtime functions.

Implementation Defined: Other system-specific entry points may exist, such as WinMain and DllMain on Windows systems.

Note: Programs targeting platforms which require a different signature for main can use a function with explicit mangling:

pragma(mangle, "main")
int myMain(int a, int b, int c)
{
return 0;
}

Function Templates

Functions can have compile time arguments in the form of a template. See function templates.

Compile Time Function Execution (CTFE)

In contexts where a compile time value is required, functions can be used to compute those values. This is called Compile Time Function Execution, or CTFE.

These contexts are:

- initialization of a static variable or a manifest constant

- static initializers of struct/class members

- dimension of a static array

- argument for a template value parameter

- static if

- static foreach

- static assert

- mixin statement

- pragma argument

- __traits argument

enum eval(alias arg) = arg;

int square(int i)
{
return i * i;
}

void main()
{
import std.stdio;

static j = square(3);      // CTFE
writeln(j);
assert(square(4) == 16);       // run time
static assert(square(3) == 9); // CTFE
writeln(eval!(square(5))); // CTFE
}

The function must have a FunctionBody.

CTFE is subject to the following restrictions:

1. Expressions may not reference any mutable static variables.

2. AsmStatements are not permitted

3. Non-portable casts (e.g., from int[] to float[]), including casts which depend on endianness, are not permitted. Casts between signed and unsigned types are permitted.

4. Reinterpretation of overlapped fields in a union is not permitted.

5. Undefined behavior must not occur.

Pointers are permitted in CTFE, provided they are used safely:

- Pointer arithmetic is permitted only on pointers which point to static or dynamic array elements. A pointer may also point to the first element past the array, although such pointers cannot be dereferenced. Pointer arithmetic on pointers which are null, or which point to a non-array, is not allowed.

- Ordered comparison (<, <=, >, >=) between two pointers is permitted when both pointers point to the same array, or when at least one pointer is null.

- Pointer comparisons between discontiguous memory blocks are illegal, unless two such comparisons are combined using && or || to yield a result which is independent of the ordering of memory blocks. Each comparison must consist of two pointer expressions compared with <, <=, >, or >=, and may optionally be negated with !. For example, the expression (p1 > q1 && p2 <= q2) is permitted when p1, p2 are expressions yielding pointers to memory block P, and q1, q2 are expressions yielding pointers to memory block Q, even when P and Q are unrelated memory blocks. It returns true if [p1..p2] lies inside [q1..q2], and false otherwise. Similarly, the expression (p1 < q1 || p2 > q2) is true if [p1..p2] lies outside [q1..q2], and false otherwise.

- Equality comparisons (==, !=, is, !is) are permitted between all pointers, without restriction.

- Any pointer may be cast to void* and from void* back to its original type. Casting between pointer and non-pointer types is illegal.

The above restrictions apply only to expressions which are actually evaluated. For example:

static int y = 0;

int countTen(int x)
{
if (x > 10)
++y;    // access static variable
return x;
}

static assert(countTen(6) == 6);    // OK
static assert(countTen(12) == 12);  // invalid, modifies y.

The __ctfe boolean pseudo-variable evaluates to true during CTFE but false otherwise.

Note: __ctfe can be used to provide an alternative execution path to avoid operations which are forbidden in CTFE. Every usage of __ctfe is statically evaluated and has no run-time cost.

Non-recoverable errors (such as assert failures) are illegal.

Implementation Defined: Executing functions via CTFE can take considerably longer than executing it at run time. If the function goes into an infinite loop, it may cause the compiler to hang.

Implementation Defined: Functions executed via CTFE can give different results from run time when implementation-defined behavior occurs.

String Mixins and Compile Time Function Execution

All functions that execute in CTFE must also be executable at run time. The compile time evaluation of a function does the equivalent of running the function at run time. The semantics of a function cannot depend on compile time values of the function. For example:

int foo(string s)
{
return mixin(s);
}

const int x = foo("1");

is illegal, because the runtime code for foo cannot be generated.
Best Practices: A function template, where s is a template argument, would be the appropriate method to implement this sort of thing.

No-GC Functions

No-GC functions are functions marked with the @nogc attribute. Those functions do not allocate memory on the GC heap. These operations are not allowed in No-GC functions:

1. constructing an array on the heap

2. resizing an array by writing to its .length property

3. array concatenation

4. array appending

5. constructing an associative array

6. indexing an associative array
Rationale: Indexing may throw a RangeError if the specified key is not present.

7. allocating an object with new on the heap
Note: new declarations of class types in function scopes are compatible with @nogc if used to initialize a scope variable, as they result in allocations on the stack.

8. calling functions that are not @nogc, unless the call is in a ConditionalStatement controlled by a DebugCondition

@nogc void foo()
{
auto a = ['a'];    // (1) error, allocates
a.length = 1;      // (2) error, array resizing allocates
a = a ~ a;         // (3) error, arrays concatenation allocates
a ~= 'c';          // (4) error, appending to arrays allocates

auto aa = ["x":1]; // (5) error, allocates
aa["abc"];         // (6) error, indexing may allocate and throws

auto p = new int;  // (7) error, operator new allocates
scope auto p = new GenericClass(); // (7) Ok
bar();             // (8) error, bar() may allocate
debug bar();       // (8) Ok
}
void bar() { }

No-GC functions can only use a closure if it is scope - see Delegates & Closures.

@nogc int delegate() foo()
{
int n;              // error, variable n cannot be allocated on heap
return (){ return n; } // since `n` escapes `foo()`, a closure is required
}

@nogc affects the type of the function. A @nogc function is covariant with a non-@nogc function.

void function() fp;
void function() @nogc gp;  // pointer to @nogc function

void foo();
@nogc void bar();

void test()
{
fp = &foo; // ok
fp = &bar; // ok, it's covariant
gp = &foo; // error, not contravariant
gp = &bar; // ok
}

Best Practices: Since a function marked @nogc will not do any GC allocations, that implies it will not cause any GC collections to run. However, another thread may still allocate with the GC and trigger a collection. The recommended way to prevent GC collections from being run is to call core.memory.GC.disable() instead. This will stop collections from being run in any thread until a corresponding call to core.memory.GC.enable() is run. GC allocations can still be performed when GC.disable() is in effect.

Function Safety

Safe Functions

Safe functions are marked with the @safe attribute. @safe can be inferred, see Function Attribute Inference.

Safe functions have safe interfaces. An implementation must enforce this by restricting the function's body to operations that are known to be safe, except for calls to @trusted functions.

The following restrictions are enforced by the compiler in safe functions:

- No casting from a pointer type T to any type U with pointers, except when:

- T implicitly converts to U

- U implements class or interface T, and both types are extern(D)

- T.opCast!U is @safe

- Both types are dynamic arrays, or both types are raw pointers, and:

- The target element type is not a pointer type

- The target element type is not mutable when the source element type is a pointer type

- Either the target element type is a dynamic array, or the target element type is no larger than the source element type

- Any source element type modifiers implicitly convert to the target element type modifiers

- Neither element type is a function type

- The target element type is not mutable when the source type is void[]

- The target element type only stores safe values

- The source element type only stores safe values when the target element type is mutable

- Neither element type is opaque.

- No casting from any non-pointer type to a pointer type.

- No pointer arithmetic (including pointer indexing & slicing).

- Cannot access array .ptr property.

- Cannot copy into a void array.

- Cannot access union fields that:

- Have pointers or references overlapping with other types

- Have invariants overlapping with other types

- Overlap with fields that could become unsafe values

- Calling any System Functions.

- No catching of exceptions that are not derived from class Exception.

- Inline assembler must be marked as @trusted.

- No explicit casting (except with a matching @safe opCast) of:

- mutable objects to immutable (except basic data types)

- immutable objects to mutable (except basic data types)

- thread local objects to shared

- shared objects to thread local

- Cannot access @system or __gshared variables.

- Cannot use void initializers for types containing:

- Pointers/reference types

- Types with invariants

- Unsafe values

Note: When indexing or slicing an array, an out of bounds access will cause a runtime error.

Functions nested inside safe functions default to being safe functions.

Safe functions are covariant with trusted or system functions.

Best Practices: Mark as many functions @safe as practical.

Safe External Functions

External functions don't have a function body visible to the compiler:

@safe extern (C) void play();

and so safety cannot be verified automatically.
Best Practices: Explicitly set an attribute for external functions rather than relying on default settings.

Trusted Functions

Trusted functions are marked with the @trusted attribute.

Like safe functions, trusted functions have safe interfaces. Unlike safe functions, this is not enforced by restrictions on the function body. Instead, it is the responsibility of the programmer to ensure that the interface of a trusted function is safe.

Example:

immutable(int)* f(int* p) @trusted
{
version (none) p[2] = 13;
// Invalid. p[2] is out of bounds. This line would exhibit undefined
// behavior.

version (none) p[1] = 13;
// Invalid. In this program, p[1] happens to be in-bounds, so the
// line would not exhibit undefined behavior, but a trusted function
// is not allowed to rely on this.

version (none) return cast(immutable) p;
// Invalid. @safe code still has mutable access and could trigger
// undefined behavior by overwriting the value later on.

int* p2 = new int;
*p2 = 42;
return cast(immutable) p2;
// Valid. After f returns, no mutable aliases of p2 can exist.
}

void main() @safe
{
int[2] a = [10, 20];
int* mp = &a[0];
immutable(int)* ip = f(mp);
assert(a[1] == 20); // Guaranteed. f cannot access a[1].
assert(ip !is mp); // Guaranteed. f cannot introduce unsafe aliasing.
}

Trusted functions may call safe, trusted, or system functions.

Trusted functions are covariant with safe or system functions.

Best Practices: Trusted functions should be kept small so that they are easier to manually verify.

System Functions

System functions are functions not marked with @safe or @trusted and are not nested inside @safe functions. System functions may be marked with the @system attribute. A function being system does not mean it actually is unsafe, it just means that its safety must be manually verified.

System functions are not covariant with trusted or safe functions.

System functions can call safe and trusted functions.

Best Practices: When in doubt, mark extern (C) and extern (C++) functions as @system when their implementations are not in D, as the D compiler will be unable to check them. Most of them are @safe, but will need to be manually checked.

Best Practices: The number and size of system functions should be minimized. This minimizes the work necessary to manually check for safety.

Safe Interfaces

When a function call's arguments, any context and accessible globals each have safe values with safe aliasing, that function has a safe interface when:

1. it cannot exhibit undefined behavior, and

2. it cannot create unsafe values that are accessible from @safe code (e.g., via return values, global variables, or ref parameters), and

3. it cannot introduce unsafe aliasing that is accessible from @safe code.

Functions that meet these requirements may be @safe or @trusted. Function that do not meet these requirements can only be @system.

Examples:

- C's free does not have a safe interface:

extern (C) @system void free(void* ptr);

because free(p) invalidates p, making its value unsafe. free can only be @system.

- C's strlen and memcpy do not have safe interfaces:

extern (C) @system size_t strlen(char* s);
extern (C) @system void* memcpy(void* dst, void* src, size_t nbytes);

because they iterate pointers based on unverified assumptions (strlen assumes that s is zero-terminated; memcpy assumes that the memory objects pointed to by dst and src are at least nbytes big). Any function that traverses a C string passed as an argument can only be @system. Any function that trusts a separate parameter for array bounds can only be @system.

- C's malloc does have a safe interface:

extern (C) @trusted void* malloc(size_t sz);

It does not exhibit undefined behavior for any input. It returns either a valid pointer, which is safe, or null which is also safe. It returns a pointer to a fresh allocation, so it cannot introduce any unsafe aliasing.
Note: The implementation of malloc is most likely @system code.

- A D version of memcpy can have a safe interface:

@safe void memcpy(E)(E[] src, E[] dst)
{
import std.math : min;
foreach (i; 0 .. min(src.length, dst.length))
{
dst[i] = src[i];
}
}

because the rules for safe values ensure that the lengths of the arrays are correct.

Safe Values

A variable or field marked as @system does not hold a safe value, regardless of its type.

For a bool, only 0 and 1 are safe values.

For all other basic data types, all possible bit patterns are safe.

A pointer is a safe value when it is one of:

1. null - see below

2. it points to a memory object that is live and the pointed to value in that memory object is safe.

Examples:

int* n = null; /* n is safe because dereferencing null must either crash
or abort. */
int* x = cast(int*) 0xDEADBEEF; /* x is (most likely) unsafe because it
is not a valid pointer and cannot be dereferenced. */

import core.stdc.stdlib: malloc, free;
int* p1 = cast(int*) malloc(int.sizeof); /* p1 is safe because the
pointer is valid and *p1 is safe regardless of its actual value. */
free(p1); /* This makes p1 unsafe. */
int** p2 = &p1; /* While it can be dereferenced, p2 is unsafe because p1
is unsafe. */
p1 = null; /* This makes p1 and p2 safe. */

A dynamic array is safe when:

1. its pointer is safe, and

2. its length is in-bounds with the corresponding memory object, and

3. all its elements are safe.

Examples:

int[] f() @system
{
bool b = true; /* b is initialized safe */
*(cast(ubyte*) &b) = 0xAA; /* b is now unsafe because it's not 0 or 1 */
int[3] a;
int[] d1 = a[0 .. 3]; /* d1 is safe. */
int[] d2 = a.ptr[0 .. 4]; /* d2 is unsafe because it goes beyond a's
bounds. */
int*[] d3 = [cast(int*) 0xDEADBEEF]; /* d3 is unsafe because the
element is unsafe. */
return d1; /* Up to here, d1 was safe, but its pointer becomes
invalid when the function returns, so the returned dynamic array
is unsafe. */
}

A static array is safe when all its elements are safe. Regardless of the element type, a static array with length zero is always safe.

An associative array is safe when all its keys and elements are safe.

A struct/union instance is safe when:

1. the values of its accessible fields are safe, and

2. it does not introduce unsafe aliasing with unions.

Examples:

void fun()
{
struct S { int* p; }
S s1 = S(new int); /* s1 is safe. */
S s2 = S(cast(int*) 0xDEADBEEF); /* s2 is unsafe, because s2.p is
unsafe. */

union U { int* p; size_t x; }
U u = U(new int); /* Even though both u.p and u.x are safe, u is unsafe
because of unsafe aliasing. */
}

A class reference is safe when it is null or:

1. it refers to a valid class instance of the class type or a type derived from the class type, and

2. the values of the instance's accessible fields are safe, and

3. it does not introduce unsafe aliasing with unions.

A function pointer is safe when it is null or it refers to a valid function that has the same or a covariant signature.

A delegate is safe when:

1. its .funcptr property is null or refers to a function that matches or is covariant with the delegate type, and

2. its .ptr property is null or refers to a memory object that is in a form expected by the function.

Null Dereferences

When generating @safe code, a compliant implementation:

- Must not assume that a null dereference will not occur. Optimizations that require that assumption cannot be used for @safe functions.

- Must generate code that will detect and abort execution:

- When a null dereference occurs on systems that by default do not protect access to the first page of memory addresses. (Note that e.g. calling mprotect is @system).

- On all systems when an expression causes a null pointer to be indexed, causing a memory access that is not prevented by the system. Warning: dmd has not implemented this yet.

Safe Aliasing

When one memory location is accessible with two different types, that aliasing is considered safe if:

1. both types are const or immutable; or

2. one of the types is mutable while the other is a const-qualified basic data type; or

3. both types are mutable basic data types; or

4. one of the types is a static array type with length zero; or

5. one of the types is a static array type with non-zero length, and aliasing of the array's element type and the other type is safe; or

6. both types are pointer types, and aliasing of the target types is safe, and the target types have the same size.

All other cases of aliasing are considered unsafe.

Note: Safe aliasing may be exposed to functions with safe interfaces without affecting their guaranteed safety. Unsafe aliasing does not guarantee safety.

Note: Safe aliasing does not imply that all aliased views of the data have safe values. Those must be examined separately for safety.

Examples:

void f1(ref ubyte x, ref float y) @safe { x = 0; y = float.init; }
union U1 { ubyte x; float y; } // safe aliasing

void test1()
{
U1 u1;
f1(u1.x, u1.y); // Ok
}

void f2(ref int* x, ref int y) @trusted { x = new int; y = 0xDEADBEEF; }
union U2 { int* x; int y; } // unsafe aliasing

void test2()
{
U2 u2;
version (none) f1(u2.x, u2.y); // not safe
}

Function Attribute Inference

FunctionLiterals, Auto Functions, Auto Ref Functions, nested functions and function templates, since their function bodies are always present, infer the following attributes unless specifically overridden:

- pure

- nothrow

- @safe

- @nogc

- return ref parameters

- scope parameters

- return scope parameters

- ref return scope parameters

Attribute inference is not done for other functions, even if the function body is present.

The inference is done by determining if the function body follows the rules of the particular attribute.

Cyclic functions (i.e. functions that wind up directly or indirectly calling themselves) are inferred as being impure, throwing, and @system.

If a function attempts to test itself for those attributes, then the function is inferred as not having those attributes.

Rationale: Function attribute inference greatly reduces the need for the user to add attributes to functions, especially for templates.

Uniform Function Call Syntax (UFCS)

A free function can be called like a member function when both:

- The member function does not (or cannot) exist for the object expression

- The free function's first parameter type matches the object expression

The object expression can be any type. This is called a UFCS function call.

void sun(T, int);

void moon(T t)
{
t.sun(1);
// If `T` does not have a member function `sun`,
// `t.sun(1)` is interpreted as if it were written `sun(t, 1)`
}

Rationale: This provides a way to add external functions to a class as if they were public final member functions. This enables minimizing the number of functions in a class to only the essentials that are needed to take care of the object's private state, without the temptation to add a kitchen-sink's worth of member functions. It also enables function chaining and component programming.
This general idea has different names in other programming languages. The following is a short list:

- C#: Extension methods

- Kotlin: Extension functions

- Zig: Dot notation

A more complex example:

stdin.byLine(KeepTerminator.yes)
.map!(a => a.idup)
.array
.sort
.copy(stdout.lockingTextWriter());

is the equivalent of:

copy(sort(array(map!(a => a.idup)(byLine(stdin, KeepTerminator.yes)))), lockingTextWriter(stdout));

UFCS works with @property functions:

@property prop(X thisObj);
@property prop(X thisObj, int value);

X obj;
obj.prop;      // if X does not have member prop, reinterpret as prop(obj);
obj.prop = 1;  // similarly, reinterpret as prop(obj, 1);

Functions declared in a local scope are not found when searching for a matching UFCS function. Neither are other local symbols, although local imports are searched:

module a;

void foo(X);
alias boo = foo;

void main()
{
void bar(X);     // bar declared in local scope
import b : baz;  // void baz(X);

X obj;
obj.foo();    // OK, calls a.foo;
//obj.bar();  // NG, UFCS does not see nested functions
obj.baz();    // OK, calls b.baz, because it is declared at the
// top level scope of module b

import b : boo = baz;
obj.boo();    // OK, calls aliased b.baz instead of a.boo (== a.foo),
// because the declared alias name 'boo' in local scope
// overrides module scope name
}

Member functions are not found when searching for a matching UFCS function.

class C
{
void mfoo(X);           // member function
static void sbar(X);    // static member function
import b : ibaz = baz;  // void baz(X);

void test()
{
X obj;
//obj.mfoo();  // NG, UFCS does not see member functions
//obj.sbar();  // NG, UFCS does not see static member functions
obj.ibaz();    // OK, ibaz is an alias of baz which is declared at
//     the top level scope of module b
}
}

Otherwise, UFCS function lookup proceeds normally.

Rationale: Local function symbols are not considered by UFCS to avoid unexpected name conflicts. See below for problematic examples.

int front(int[] arr) { return arr[0]; }

void main()
{
int[] a = [1,2,3];
auto x = a.front();   // call .front by UFCS

auto front = x;       // front is now a variable
auto y = a.front();   // Error, front is not a function
}

class C
{
int[] arr;
int front()
{
return arr.front(); // Error, C.front is not callable
// using argument types (int[])
}
}

)

Operator Overloading

Operator overloading is accomplished by rewriting operators whose operands are class or struct objects into calls to specially named members. No additional syntax is used.

Unary Operator Overloading

Overloadable Unary Operators

op

rewrite

-e

e.opUnary!("-")()

+e

e.opUnary!("+")()

~e

e.opUnary!("~")()

*e

e.opUnary!("*")()

++e

e.opUnary!("++")()

--e

e.opUnary!("--")()

For example, in order to overload the - (negation) operator for struct S, and no other operator:

struct S
{
int m;

int opUnary(string s)() if (s == "-")
{
return -m;
}
}

void main()
{
S s = {2};
assert(-s == -2);
}

Note: opUnary above can also be declared using a template parameter specialization:

int opUnary(string s : "-")()

Postincrement e++ and Postdecrement e-- Operators

These are not directly overloadable, but instead are rewritten in terms of the ++e and --e prefix operators:

Postfix Operator Rewrites

op

rewrite

e--

(auto t = e, e.opUnary!"--", t)

e++

(auto t = e, e.opUnary!"++", t)

Overloading Index Unary Operators

Indexing can be overloaded. A unary operation on an index expression can also be overloaded independently. This works for multidimensional indexing.

Overloadable Index Unary Operators

op

rewrite

-a[b_1, b_2, ... bn]

a.opIndexUnary!("-")(b_1, b_2, ... bn)

+a[b_1, b_2, ... bn]

a.opIndexUnary!("+")(b_1, b_2, ... bn)

~a[b_1, b_2, ... bn]

a.opIndexUnary!("~")(b_1, b_2, ... bn)

*a[b_1, b_2, ... bn]

a.opIndexUnary!("*")(b_1, b_2, ... bn)

++a[b_1, b_2, ... bn]

a.opIndexUnary!("++")(b_1, b_2, ... bn)

--a[b_1, b_2, ... bn]

a.opIndexUnary!("--")(b_1, b_2, ... bn)

struct S
{
private int[] a;

void opIndexUnary(string s: "++")(size_t i) { ++a[i]; }
}

S s = {[4]};
++s[0];
assert(s.a[0] == 5);

Overloading Slice Unary Operators

Slicing can be overloaded. A unary operation on a slice can also be overloaded independently. opIndexUnary is defined either with no function arguments for a full slice, or with two arguments for the start and end indices of the slice.

Overloadable Slice Unary Operators

op

rewrite

-a[i..j]

a.opIndexUnary!("-")(a.opSlice(i, j))

+a[i..j]

a.opIndexUnary!("+")(a.opSlice(i, j))

~a[i..j]

a.opIndexUnary!("~")(a.opSlice(i, j))

*a[i..j]

a.opIndexUnary!("*")(a.opSlice(i, j))

++a[i..j]

a.opIndexUnary!("++")(a.opSlice(i, j))

--a[i..j]

a.opIndexUnary!("--")(a.opSlice(i, j))

-a[ ]

a.opIndexUnary!("-")()

+a[ ]

a.opIndexUnary!("+")()

~a[ ]

a.opIndexUnary!("~")()

*a[ ]

a.opIndexUnary!("*")()

++a[ ]

a.opIndexUnary!("++")()

--a[ ]

a.opIndexUnary!("--")()

struct S
{
private int[] a;

void opIndexUnary(string s: "--")() { --a[]; }
}

S s = {[1, 2]};
--s[];
assert(s.a == [0, 1]);

Note: For backward compatibility, if the above rewrites fail to compile and opSliceUnary is defined, then the rewrites a.opSliceUnary!(op)(i, j) and a.opSliceUnary!(op) are tried instead, respectively.

Cast Operator Overloading

To define how one type can be cast to another, define the opCast template method, which is used as follows:

Cast Operators

op

rewrite

cast(type) e

e.opCast!(type)()

Note that opCast is only ever used with an explicit cast expression, except in the case of boolean operations (see next section).

struct S
{
void* mem;

bool opCast(T)()
if (is(T == bool)) => mem !is null;
}

S s = S(new int);
auto b = cast(bool) s;
assert(b);
//b = s; // error

If the return type of opCast differs from the type parameter of the cast, then the result is implicitly converted to type.

Boolean Operations

Notably absent from the list of overloaded unary operators is the ! logical negation operator. More obscurely absent is a unary operator to convert to a bool result. Instead, for structs these are covered by a rewrite to:

opCast!(bool)(e)

So,

if (e)   =>  if (e.opCast!(bool))
if (!e)  =>  if (!e.opCast!(bool))

and similarly for other boolean conditional expressions and logical operators used on the struct instance.

This only happens, however, for instances of structs. Class references are converted to bool by checking to see if the class reference is null or not.

Binary Operator Overloading

The following binary operators are overloadable:

Overloadable Binary Operators

+

-

*

/

%

^^

&

|

^

<<

>>

>>>

~

in

The expression:

a op b

is rewritten as one of:

a.opBinary!("op")(b)
b.opBinaryRight!("op")(a)

and the one with the ‘better’ match is selected. It is an error for both to equally match. Example:

struct S
{
int[] data;

// this ~ rhs
int[] opBinary(string op : "~")(int rhs)
{
return data ~ rhs;
}
// lhs ~ this
int[] opBinaryRight(string op : "~")(int lhs)
{
return lhs ~ data;
}
}

void main()
{
auto s = S([2,3]);
assert(s ~ 4 == [2,3,4]); // opBinary
assert(1 ~ s == [1,2,3]); // opBinaryRight
}

Operator overloading for a number of operators can be done at the same time. For example, if only the + or - operators are supported:

T opBinary(string op)(T rhs)
{
static if (op == "+") return data + rhs.data;
else static if (op == "-") return data - rhs.data;
else static assert(0, "Operator " ~ op ~ " not implemented");
}

To do them all en masse:

T opBinary(string op)(T rhs)
{
return mixin("data " ~ op ~ " rhs.data");
}

Note that opIn and opIn_r have been deprecated in favor of opBinary!"in" and opBinaryRight!"in" respectively.

Overloading the Comparison Operators

D allows overloading of the comparison operators ==, !=, <, <=, >=, > via two functions, opEquals and opCmp.

The equality and inequality operators are treated separately from comparison operators because while practically all user-defined types can be compared for equality, only a subset of types have a meaningful ordering. For example, while it makes sense to determine if two RGB color vectors are equal, it is not meaningful to say that one color is greater than another, because colors do not have an ordering. Thus, one would define opEquals for a Color type, but not opCmp.

Furthermore, even with orderable types, the order relation may not be linear. For example, one may define an ordering on sets via the subset relation, such that x < y is true if x is a (strict) subset of y. If x and y are disjoint sets, then neither x < y nor y < x holds, but that does not imply that x == y. Thus, it is insufficient to determine equality purely based on opCmp alone. For this reason, opCmp is only used for the RelExpression operators <, <=, >=, and >. The equality operators == and != always employ opEquals instead.

Therefore, it is the programmer's responsibility to ensure that opCmp and opEquals are consistent with each other. If opEquals is not specified for a struct, the compiler provides a default version of it that does member-wise comparison. If this suffices, one may define only opCmp to customize the behaviour of the RelExpression operators. But if not, then a custom version of opEquals should be defined as well, in order to preserve consistent semantics between the two kinds of comparison operators.

Finally, if a user-defined type is to be used as a key in the built-in associative arrays, then the programmer must ensure that the semantics of opEquals and toHash are consistent. If not, the associative array may not work in the expected manner.

Overloading == and !=

Expressions of the form a != b are rewritten as !(a == b).

Given a == b :

1. If a and b are both class objects, then the expression is rewritten as:

.object.opEquals(a, b)

and that function is similar to:

bool opEquals(Object a, Object b)
{
if (a is b) return true;
if (a is null || b is null) return false;
if (typeid(a) == typeid(b)) return a.opEquals(b);
return a.opEquals(b) && b.opEquals(a);
}

2. Otherwise the expressions a.opEquals(b) and b.opEquals(a) are tried. If both resolve to the same opEquals function, then the expression is rewritten to be a.opEquals(b).

3. If one is a better match than the other, or one compiles and the other does not, the first is selected.

4. Otherwise, an error results.

If overriding Object.opEquals() for classes, the class member function should take an Object parameter and dynamically check that it is a compatible class, e.g.:

class C
{
int i;

this(int i) { this.i = i; }

override bool opEquals(Object o)
{
if (auto c = cast(C) o)
return c.i == i;
else
assert(0, __FUNCTION__ ~ ": Cannot compare a " ~ typeid(o).toString);
}
}

static assert(new C(2) == new C(2));
static assert(new C(2) != new C(3));

If structs declare an opEquals member function for the identity comparison, it could have several forms, such as:

struct S
{
// lhs should be mutable object
bool opEquals(const S s) { ... }        // for r-values (e.g. temporaries)
bool opEquals(ref const S s) { ... }    // for l-values (e.g. variables)

// both hand side can be const object
bool opEquals(const S s) const { ... }  // for r-values (e.g. temporaries)
}

Alternatively, declare a single templated opEquals function with an auto ref parameter:

struct S
{
// for l-values and r-values,
// with converting both hand side implicitly to const
bool opEquals()(auto ref const S s) const { ... }
}

Note: See Struct Equality for best practices on implementing opEquals for structs.

Overloading <, <=, >, and >=

Class references are first compared by reference. If they refer to different objects and neither is null, they are then compared by calling a matching opCmp method, as for structs.

Struct comparison operations are rewritten as follows:

Rewriting of comparison operations

comparison

rewrite 1

rewrite 2

a < b

a.opCmp(b) < 0

b.opCmp(a) > 0

a <= b

a.opCmp(b) <= 0

b.opCmp(a) >= 0

a > b

a.opCmp(b) > 0

b.opCmp(a) < 0

a >= b

a.opCmp(b) >= 0

b.opCmp(a) <= 0

Both rewrites are tried. If only one compiles, that one is taken. If they both resolve to the same function, the first rewrite is done. If they resolve to different functions, the best matching one is used. If they both match the same, but are different functions, an ambiguity error results.

struct B
{
int opCmp(int)         { return -1; }
int opCmp(ref const S) { return -1; }
int opCmp(ref const C) { return -1; }
}

struct S
{
int opCmp(ref const S) { return 1; }
int opCmp(ref B)       { return 0; }
}

struct C
{
int opCmp(ref const B) { return 0; }
}

void main()
{
S s;
const S cs;
B b;
C c;
assert(s > s);      // s.opCmp(s) > 0
assert(!(s < b));   // s.opCmp(b) > 0  - S.opCmp(ref B) is exact match
assert(!(b < s));   // s.opCmp(b) < 0  - S.opCmp(ref B) is exact match
assert(b < cs);     // b.opCmp(s) < 0  - B.opCmp(ref const S) is  exact match
static assert(!__traits(compiles, b < c)); // both C.opCmp and B.opcmp match exactly
}

If overriding Object.opCmp() for classes, the class member function should take an Object parameter and dynamically check that it is a compatible class for the comparison (like when overriding opEquals).

class C
{
override int opCmp(Object o) { ... }
}

If structs declare an opCmp member function, it should have the following form:

struct S
{
int opCmp(ref const S s) const { ... }
}

Note that opCmp is only used for RelExpression operators; expressions like a == b always use opEquals.

For structs, if opCmp is defined but opEquals isn't, the compiler will supply a default version of opEquals that performs member-wise comparison. However, the member-wise comparison may not be consistent with the user-defined opCmp.

It is up to the programmer to also supply a version of opEquals when appropriate. Otherwise, a RelExpression like a <= b will behave inconsistently with equalities like a == b.

struct S
{
int i, j;
int opCmp(ref const S s) const { return (i > s.i) - (i < s.i); } // ignore j
}

S a = {2, 3};
S b = {2, 1};
S c = {3, 0};
assert(a < c);
assert(a <= b);
assert(!(a < b)); // opCmp ignores j
assert(a != b);   // generated opEquals tests both i and j members

Best Practices: Using (i > s.i) - (i < s.i) instead of i - s.i to compare integers avoids overflow.

Function Call Operator Overloading

The function call operator, (), can be overloaded by declaring a function named opCall:

struct F
{
int opCall();
int opCall(int x, int y, int z);
}

void main()
{
F f;
int i;

i = f();      // same as i = f.opCall();
i = f(3,4,5); // same as i = f.opCall(3,4,5);
}

In this way a struct or class object can behave as if it were a function.

Note that merely declaring opCall automatically disables struct literal syntax. To avoid the limitation, declare a constructor so that it takes priority over opCall in Type(...) syntax.

struct Multiplier
{
int factor;
this(int num) { factor = num; }
int opCall(int value) { return value * factor; }
}

void main()
{
Multiplier m = Multiplier(10);  // invoke constructor
assert(m.factor == 10);
int result = m(5);              // invoke opCall
assert(result == 50);
}

Static opCall

static opCall also works as expected for a function call operator with type names.

struct Double
{
static int opCall(int x) { return x * 2; }
}

void main()
{
int i = Double(2);
assert(i == 4);
}

Mixing struct constructors and static opCall is not allowed.

struct S
{
this(int i) {}
static S opCall()  // disallowed due to constructor
{
return S.init;
}
}

Note: static opCall can be used to simulate struct constructors with no arguments, but this is not recommended practice. Instead, the preferred solution is to use a factory function to create struct instances.

Assignment Operator Overloading

The assignment operator = can be overloaded if the left hand side is a struct aggregate, and opAssign is a member function of that aggregate.
For struct types, operator overloading for the identity assignment is allowed.

struct S
{
// identity assignment, allowed.
void opAssign(S rhs);

// not identity assignment, also allowed.
void opAssign(int);
}
S s;
s = S();      // Rewritten to s.opAssign(S());
s = 1;        // Rewritten to s.opAssign(1);

However for class types, identity assignment is not allowed. All class types have reference semantics, so identity assignment by default rebinds the left-hand-side to the argument at the right, and this is not overridable.

class C
{
// If X is the same type as C or the type which is
// implicitly convertible to C, then opAssign would
// accept identity assignment, which is disallowed.
// C opAssign(...);
// C opAssign(X);
// C opAssign(X, ...);
// C opAssign(X ...);
// C opAssign(X, U = defaultValue, etc.);

// not an identity assignment - allowed
void opAssign(int);
}
C c = new C();
c = new C();  // Rebinding referencee
c = 1;        // Rewritten to c.opAssign(1);

Index Assignment Operator Overloading

If the left hand side of an assignment is an index operation on a struct or class instance, it can be overloaded by providing an opIndexAssign member function. Expressions of the form a[b_1, b_2, ... bn] = c are rewritten as a.opIndexAssign(c, b_1, b_2, ... bn).

struct A
{
int opIndexAssign(int value, size_t i1, size_t i2);
}

void test()
{
A a;
a[i,3] = 7;  // same as a.opIndexAssign(7,i,3);
}

Slice Assignment Operator Overloading

If the left hand side of an assignment is a slice operation on a struct or class instance, it can be overloaded by implementing an opIndexAssign member function that takes the return value of the opSlice function as parameter(s). Expressions of the form a[i..j] = c are rewritten as a.opIndexAssign(c, a.opSlice!0(i, j)), and a[] = c as a.opIndexAssign(c).

See Array Indexing and Slicing Operators Overloading for more details.

struct A
{
int opIndexAssign(int v);  // overloads a[] = v
int opIndexAssign(int v, size_t[2] slice);  // overloads a[i .. j] = v
size_t[2] opSlice(size_t dim)(size_t i, size_t j);  // overloads i .. j
}

void test()
{
A a;
int v;

a[] = v;  // same as a.opIndexAssign(v);
a[3..4] = v;  // same as a.opIndexAssign(v, a.opSlice!0(3,4));
}

For backward compatibility, if rewriting a[i..j] as a.opIndexAssign(a.opSlice!0(i, j)) fails to compile, the legacy rewrite opSliceAssign(c, i, j) is used instead.

Op Assignment Operator Overloading

The following op assignment operators are overloadable:

Overloadable Op Assignment Operators

+=

-=

*=

/=

%=

^^=

&=

|=

^=

<<=

>>=

>>>=

~=

The expression:

a op= b

is rewritten as:

a.opOpAssign!("op")(b)

Example:

struct S
{
int i;
void opOpAssign(string op: "+")(int rhs) { i += rhs; }
}

S s = {2};
s += 3;
assert(s.i == 5);

Index Op Assignment Operator Overloading

If the left hand side of an op= is an index expression on a struct or class instance and opIndexOpAssign is a member:

a[b_1, b_2, ... bn] op= c

it is rewritten as:

a.opIndexOpAssign!("op")(c, b_1, b_2, ... bn)

Slice Op Assignment Operator Overloading

If the left hand side of an op= is a slice expression on a struct or class instance and opIndexOpAssign is a member:

a[i..j] op= c

it is rewritten as:

a.opIndexOpAssign!("op")(c, a.opSlice(i, j))

and

a[] op= c

it is rewritten as:

a.opIndexOpAssign!("op")(c)

For backward compatibility, if the above rewrites fail and opSliceOpAssign is defined, then the rewrites a.opSliceOpAssign(c, i, j) and a.opSliceOpAssign(c) are tried, respectively.

Array Indexing and Slicing Operators Overloading

The array indexing and slicing operators are overloaded by implementing the opIndex, opSlice, and opDollar methods. These may be combined to implement multidimensional arrays.

Index Operator Overloading

Expressions of the form arr[b_1, b_2, ... bn] are translated into arr.opIndex(b_1, b_2, ... bn). For example:

struct A
{
int opIndex(size_t i1, size_t i2, size_t i3);
}

void test()
{
A a;
int i;
i = a[5,6,7];  // same as i = a.opIndex(5,6,7);
}

In this way a struct or class object can behave as if it were an array.

If an index expression can be rewritten using opIndexAssign or opIndexOpAssign, those are preferred over opIndex.

Slice Operator Overloading

Overloading the slicing operator means overloading expressions like a[] or a[i..j], where the expressions inside the square brackets contain slice expressions of the form i..j.

To overload a[], simply define opIndex with no parameters:

struct S
{
int[] impl;
int[] opIndex()
{
return impl[];
}
}

void main()
{
auto s = S([1,2,3]);
int[] t = s[]; // calls s.opIndex()
assert(t == [1,2,3]);
}

To overload array slicing of the form a[i..j], two steps are needed. First, the expressions of the form i..j are translated via opSlice!0 into objects that encapsulate the endpoints i and j. Then these objects are passed to opIndex to perform the actual slicing.

struct S
{
int[] impl;

int[] opSlice(size_t dim: 0)(size_t i, size_t j)
{
return impl[i..j];
}
int[] opIndex()(int[] slice) { return slice; }
}

void main()
{
auto s = S([1, 2, 3]);
int[] t = s[0..2]; // calls s.opIndex(s.opSlice!0(0, 2))
assert(t == [1, 2]);
}

This design was chosen in order to support mixed indexing and slicing in multidimensional arrays; for example, in translating expressions like arr[1, 2..3, 4]. More precisely, an expression of the form arr[b_1, b_2, ... bn] is translated into arr.opIndex(c_1, c_2, ... cn). Each argument bi can be either a single expression, in which case it is passed directly as the corresponding argument ci to opIndex; or it can be a slice expression of the form xi..yi, in which case the corresponding argument ci to opIndex is arr.opSlice!i(xi, yi). Namely:

op

rewrite

arr[1, 2, 3]

arr.opIndex(1, 2, 3)

arr[1..2, 3..4, 5..6]

arr.opIndex(arr.opSlice!0(1,2), arr.opSlice!1(3,4), arr.opSlice!2(5,6))

arr[1, 2..3, 4]

arr.opIndex(1, arr.opSlice!1(2,3), 4)

Similar translations are done for assignment operators involving slicing, for example:

op

rewrite

arr[1, 2..3, 4] = c

arr.opIndexAssign(c, 1, arr.opSlice!1(2, 3), 4)

arr[2, 3..4] += c

arr.opIndexOpAssign!"+"(c, 2, arr.opSlice!1(2, 3))

The intention is that opSlice!i should return a user-defined object that represents an interval of indices along the i'th dimension of the array. This object is then passed to opIndex to perform the actual slicing operation. If only one-dimensional slicing is desired, opSlice may be declared without the compile-time parameter i.

Note that in all cases, arr is only evaluated once. Thus, an expression like getArray()[1, 2..3, $-1]=c has the effect of:

auto __tmp = getArray();
__tmp.opIndexAssign(c, 1, __tmp.opSlice!1(2,3), __tmp.opDollar!2 - 1);

where the initial function call to getArray is only executed once.

Note: For backward compatibility, a[] and a[i..j] can also be overloaded by implementing opSlice() with no arguments and opSlice(i, j) with two arguments, respectively. This only applies for one-dimensional slicing, and dates from when D did not have full support for multidimensional arrays. This usage of opSlice is discouraged.

Dollar Operator Overloading

Within the arguments to array index and slicing operators, $ gets translated to opDollar!i, where i is the position of the expression $ appears in. For example:

op

rewrite

arr[$-1, $-2, 3]

arr.opIndex(arr.opDollar!0 - 1, arr.opDollar!1 - 2, 3)

arr[1, 2, 3..$]

arr.opIndex(1, 2, arr.opSlice!2(3, arr.opDollar!2))

The intention is that opDollar!i should return the length of the array along its i'th dimension, or a user-defined object representing the end of the array along that dimension, that is understood by opSlice and opIndex.

struct Rectangle
{
int width, height;
int[][] impl;

this(int w, int h)
{
width = w;
height = h;
impl = new int[][](w, h);
}
int opIndex(size_t i1, size_t i2)
{
return impl[i1][i2];
}
int opDollar(size_t pos)()
{
static if (pos==0)
return width;
else
return height;
}
}

void main()
{
auto r = Rectangle(10,20);
int i = r[$-1, 0];    // same as: r.opIndex(r.opDollar!0, 0),
// which is r.opIndex(r.width-1, 0)
int j = r[0, $-1];    // same as: r.opIndex(0, r.opDollar!1)
// which is r.opIndex(0, r.height-1)
}

As the above example shows, a different compile-time argument is passed to opDollar depending on which argument it appears in. A $ appearing in the first argument gets translated to opDollar!0, a $ appearing in the second argument gets translated to opDollar!1, and so on. Thus, the appropriate value for $ can be returned to implement multidimensional arrays.

Note that opDollar!i is only evaluated once for each i where $ occurs in the corresponding position in the indexing operation. Thus, an expression like arr[$-sqrt($), 0, $-1] has the effect of:

auto __tmp1 = arr.opDollar!0;
auto __tmp2 = arr.opDollar!2;
arr.opIndex(__tmp1 - sqrt(__tmp1), 0, __tmp2 - 1);

If opIndex is declared with only one argument, the compile-time argument to opDollar may be omitted. In this case, it is illegal to use $ inside an array indexing expression with more than one argument.

Complete Example

The code example below shows a simple implementation of a 2-dimensional array with overloaded indexing and slicing operators. The explanations of the various constructs employed are given in the sections following.

struct Array2D(E)
{
E[] impl;
int stride;
int width, height;

this(int width, int height, E[] initialData = [])
{
impl = initialData;
this.stride = this.width = width;
this.height = height;
impl.length = width * height;
}

// Index a single element, e.g., arr[0, 1]
ref E opIndex(int i, int j) { return impl[i + stride*j]; }

// Array slicing, e.g., arr[1..2, 1..2], arr[2, 0..$], arr[0..$, 1].
Array2D opIndex(int[2] r1, int[2] r2)
{
Array2D result;

auto startOffset = r1[0] + r2[0]*stride;
auto endOffset = r1[1] + (r2[1] - 1)*stride;
result.impl = this.impl[startOffset .. endOffset];

result.stride = this.stride;
result.width = r1[1] - r1[0];
result.height = r2[1] - r2[0];

return result;
}
auto opIndex(int[2] r1, int j) { return opIndex(r1, [j, j+1]); }
auto opIndex(int i, int[2] r2) { return opIndex([i, i+1], r2); }

// Support for `x..y` notation in slicing operator for the given dimension.
int[2] opSlice(size_t dim)(int start, int end)
if (dim >= 0 && dim < 2)
in { assert(start >= 0 && end <= this.opDollar!dim); }
do
{
return [start, end];
}

// Support `$` in slicing notation, e.g., arr[1..$, 0..$-1].
@property int opDollar(size_t dim : 0)() { return width; }
@property int opDollar(size_t dim : 1)() { return height; }
}

void main()
{
auto arr = Array2D!int(4, 3, [
0, 1, 2,  3,
4, 5, 6,  7,
8, 9, 10, 11
]);

// Basic indexing
assert(arr[0, 0] == 0);
assert(arr[1, 0] == 1);
assert(arr[0, 1] == 4);

// Use of opDollar
assert(arr[$-1, 0] == 3);
assert(arr[0, $-1] == 8);   // Note the value of $ differs by dimension
assert(arr[$-1, $-1] == 11);

// Slicing
auto slice1 = arr[1..$, 0..$];
assert(slice1[0, 0] == 1 && slice1[1, 0] == 2  && slice1[2, 0] == 3 &&
slice1[0, 1] == 5 && slice1[1, 1] == 6  && slice1[2, 1] == 7 &&
slice1[0, 2] == 9 && slice1[1, 2] == 10 && slice1[2, 2] == 11);

auto slice2 = slice1[0..2, 1..$];
assert(slice2[0, 0] == 5 && slice2[1, 0] == 6 &&
slice2[0, 1] == 9 && slice2[1, 1] == 10);

// Thin slices
auto slice3 = arr[2, 0..$];
assert(slice3[0, 0] == 2 &&
slice3[0, 1] == 6 &&
slice3[0, 2] == 10);

auto slice4 = arr[0..3, 2];
assert(slice4[0, 0] == 8 && slice4[1, 0] == 9 && slice4[2, 0] == 10);
}

Forwarding

Member names not found in a class or struct can be forwarded to a template function named opDispatch for resolution.

import std.stdio;

struct S
{
void opDispatch(string s, T)(T i)
{
writefln("S.opDispatch('%s', %s)", s, i);
}
}

class C
{
void opDispatch(string s)(int i)
{
writefln("C.opDispatch('%s', %s)", s, i);
}
}

struct D
{
template opDispatch(string s)
{
enum int opDispatch = 8;
}
}

void main()
{
S s;
s.opDispatch!("hello")(7);
s.foo(7);

auto c = new C();
c.foo(8);

D d;
writefln("d.foo = %s", d.foo);
assert(d.foo == 8);
}

D1 style operator overloading

The D1 operator overload mechanisms are deprecated.

Templates

Template Declarations

Templates are D's approach to generic programming. Templates can be defined with a TemplateDeclaration:

TemplateDeclaration:
template Identifier TemplateParameters Constraintopt { DeclDefsopt }

TemplateParameters:
( TemplateParameterListopt )

TemplateParameterList:
TemplateParameter
TemplateParameter ,
TemplateParameter , TemplateParameterList

The DeclDefs body of the template must be syntactically correct even if never instantiated. Semantic analysis is not done until instantiation. A template forms its own scope, and the template body can contain declarations such as classes, structs, types, enums, variables, functions, and other templates.

Template parameters can take types, values, symbols, or sequences.

template t(T) // declare type parameter T
{
T v; // declare a member variable of type T within template t
}

A template parameter can have a specialization which constrains an argument the TemplateParameter can accept.

template t(T : int) // type T must implicitly convert to int
{
...
}

If multiple templates with the same Identifier are declared, they are distinct if they have different parameters or are differently specialized.

If a template has a member which has the same identifier as the template, the template is an Eponymous Template. template declarations with one eponymous member are usually written as specific short syntax template declarations instead.

Template Instantiation

A template must be instantiated before use. This means passing an argument list to the template. Those arguments are typically then substituted into the template body, which becomes a new scoped entity.

Function templates can be implicitly instantiated if the compiler can infer the template arguments from a function call. Otherwise the template must be instantiated explicitly.

Explicit Template Instantiation

Templates are explicitly instantiated using a ! after the template name, then either an argument list or a single token argument.

TemplateInstance:
Identifier TemplateArguments

TemplateArguments:
! ( TemplateArgumentListopt )
! TemplateSingleArgument

TemplateArgumentList:
TemplateArgument
TemplateArgument ,
TemplateArgument , TemplateArgumentList

TemplateSingleArgument:
Identifier
FundamentalType
CharacterLiteral
StringLiteral
InterpolationExpressionSequence
IntegerLiteral
FloatLiteral
true
false
null
this
SpecialKeyword

A template argument can be a type, compile-time expression or a symbol.

TemplateArgument:
Type
AssignExpression
Symbol

Symbol:
SymbolTail
. SymbolTail

SymbolTail:
Identifier
Identifier . SymbolTail
TemplateInstance
TemplateInstance . SymbolTail

Once instantiated, the declarations inside the template, called the template members, are in the scope of the TemplateInstance:

template TFoo(T) { alias Ptr = T*; }
...
TFoo!(int).Ptr x; // declare x to be of type int*

If the TemplateArgument is one token long, the parentheses can be omitted:

TFoo!int.Ptr x;   // same as TFoo!(int).Ptr x;

A template instantiation can be aliased:

template TFoo(T) { alias Ptr = T*; }
alias foo = TFoo!(int);
foo.Ptr x;        // declare x to be of type int*

Common Instantiation

Multiple instantiations of a TemplateDeclaration with the same TemplateArgumentList will all refer to the same instantiation. For example:

template TFoo(T) { T f; }
alias a = TFoo!(int);
alias b = TFoo!(int);
...
a.f = 3;
assert(b.f == 3);  // a and b refer to the same instance of TFoo

This is true even if the TemplateInstances are done in different modules.

Even if template arguments are implicitly converted to the same template parameter type, they still refer to the same instance. This example uses a TemplateValueParameter and a struct template:

struct TFoo(int x) { }

// Different template parameters create different struct types
static assert(!is(TFoo!(3) == TFoo!(2)));
// 3 and 2+1 are both 3 of type int - same TFoo instance
static assert(is(TFoo!(3) == TFoo!(2 + 1)));

// 3u is implicitly converted to 3 to match int parameter,
// and refers to exactly the same instance as TFoo!(3)
static assert(is(TFoo!(3) == TFoo!(3u)));

Practical Example

A simple generic copy template would be:

template TCopy(T)
{
void copy(out T to, T from)
{
to = from;
}
}

To use this template, it must first be instantiated with a specific type:

int i;
TCopy!(int).copy(i, 3);

See also function templates.

Instantiation Scope

TemplateInstances are always instantiated in the scope of where the TemplateDeclaration is declared, with the addition of the template parameters being declared as aliases for their deduced types.

Example:
module a:

template TFoo(T) { void bar() { func(); } }

module b:

import a;

void func() { }
alias f = TFoo!(int); // error: func not defined in module a

Example:
module a:

template TFoo(T) { void bar() { func(1); } }
void func(double d) { }

module b:

import a;

void func(int i) { }
alias f = TFoo!(int);
...
f.bar();  // will call a.func(double)

TemplateParameter specializations and default arguments are evaluated in the scope of the TemplateDeclaration.

Template Parameters

TemplateParameter:
TemplateTypeParameter
TemplateValueParameter
TemplateAliasParameter
TemplateSequenceParameter
TemplateThisParameter

Template parameters can take types, values, symbols, or sequences.

- Type parameters can take any type.

- Value parameters can take any expression which can be statically evaluated at compile time.

- Alias parameters can take almost any symbol.

- Sequence parameters can take zero or more types, values or symbols.

A default argument specifies the type, value or symbol to use for the TemplateParameter when a matching argument is not supplied.

Type Parameters

TemplateTypeParameter:
Identifier TemplateTypeParameterSpecializationopt TemplateTypeParameterDefaultopt

TemplateTypeParameterSpecialization:
: Type

TemplateTypeParameterDefault:
= Type

Specialization and Pattern Matching

Templates may be specialized for particular types of arguments by following the template parameter identifier with a : and the pattern for the specialized type. For example:

template TFoo(T)        { ... } // #1
template TFoo(T : T[])  { ... } // #2
template TFoo(T : char) { ... } // #3
template TFoo(T, U, V)  { ... } // #4

alias foo1 = TFoo!(int);            // instantiates #1
alias foo2 = TFoo!(double[]);       // instantiates #2 matching pattern T[] with T being double
alias foo3 = TFoo!(char);           // instantiates #3
alias fooe = TFoo!(char, int);      // error, number of arguments mismatch
alias foo4 = TFoo!(char, int, int); // instantiates #4

The template picked to instantiate is the one that is most specialized that fits the types of the TemplateArgumentList. If the result is ambiguous, it is an error.

Type Parameter Deduction

The types of template parameters are deduced for a particular template instantiation by comparing the template argument with the corresponding template parameter.

For each template parameter, the following rules are applied in order until a type is deduced for each parameter:

1. If there is no type specialization for the parameter, the type of the parameter is set to the template argument.

2. If the type specialization is dependent on a type parameter, the type of that parameter is set to be the corresponding part of the type argument.

3. If after all the type arguments are examined, there are any type parameters left with no type assigned, they are assigned types corresponding to the template argument in the same position in the TemplateArgumentList.

4. If applying the above rules does not result in exactly one type for each template parameter, then it is an error.

For example:

template TFoo(T) { }
alias foo1 = TFoo!(int);     // (1) T is deduced to be int
alias foo2 = TFoo!(char*);   // (1) T is deduced to be char*

template TBar(T : T*) { }    // match template argument against T* pattern
alias bar = TBar!(char*);    // (2) T is deduced to be char

template TAbc(D, U : D[]) { }    // D[] is pattern to be matched
alias abc1 = TAbc!(int, int[]);  // (2) D is deduced to be int, U is int[]
alias abc2 = TAbc!(char, int[]); // (4) error, D is both char and int

template TDef(D : E*, E) { }   // E* is pattern to be matched
alias def = TDef!(int*, int);  // (1) E is int
// (3) D is int*

Deduction from a specialization can provide values for more than one parameter:

template Foo(T: T[U], U)
{
...
}

Foo!(int[long])  // instantiates Foo with T set to int, U set to long

When considering matches, a class is considered to be a match for any super classes or interfaces:

class A { }
class B : A { }

template TFoo(T : A) { }
alias foo = TFoo!(B);      // (3) T is B

template TBar(T : U*, U : A) { }
alias bar = TBar!(B*, B);  // (2) T is B*
// (3) U is B

This Parameters

TemplateThisParameter:
this TemplateTypeParameter

TemplateThisParameters are used in member function templates to pick up the type of the this reference. It also will infer the mutability of the this reference. For example, if this is const, then the function is marked const.

struct S
{
void foo(this T)() const
{
pragma(msg, T);
}
}

void main()
{
const(S) s;
(&s).foo();
S s2;
s2.foo();
immutable(S) s3;
s3.foo();
}

Prints:

const(S)
S
immutable(S)

Avoiding Runtime Type Checks

TemplateThisParameter is especially useful when used with inheritance. For example, consider the implementation of a final base method which returns a derived class type. Typically this would return a base type, but that would prohibit calling or accessing derived properties of the type:

interface Addable(T)
{
final auto add(T t)
{
return this;
}
}

class List(T) : Addable!T
{
List remove(T t)
{
return this;
}
}

void main()
{
auto list = new List!int;
list.add(1).remove(1);  // error: no 'remove' method for Addable!int
}

Here the method add returns the base type, which doesn't implement the remove method. The template this parameter can be used for this purpose:

interface Addable(T)
{
final R add(this R)(T t)
{
return cast(R)this;  // cast is necessary, but safe
}
}

class List(T) : Addable!T
{
List remove(T t)
{
return this;
}
}

void main()
{
auto list = new List!int;
static assert(is(typeof(list.add(1)) == List!int));
list.add(1).remove(1);  // ok, List.add

Addable!int a = list;
// a.add calls Addable.add
static assert(is(typeof(a.add(1)) == Addable!int));
}

Value Parameters

TemplateValueParameter:
BasicType Declarator TemplateValueParameterSpecializationopt TemplateValueParameterDefaultopt

TemplateValueParameterSpecialization:
: ConditionalExpression

TemplateValueParameterDefault:
= AssignExpression
= SpecialKeyword

A template value parameter can take an argument of any expression which can be statically evaluated at compile time as one of the following:

- A bool, character, integer, floating point or string value

- null

- An array literal whose elements could be valid template value arguments

- An associative array literal whose keys and values could each be valid template value arguments

- A struct literal whose arguments could be valid template value arguments

template foo(string s)
{
enum string bar = s ~ " betty";
}

void main()
{
import std.stdio;
writeln(foo!("hello").bar); // prints: hello betty
}

Specialization

Any specialization or default expression provided must be evaluatable at compile-time.

In this example, template foo has a value parameter that is specialized for 10:

template foo(U : int, int v : 10)
{
U x = v;
}

void main()
{
assert(foo!(int, 10).x == 10);
static assert(!__traits(compiles, foo!(int, 11)));
}

This can be useful when a different template body is required for a specific value. Another template overload would be defined to take other integer literal values.

Alias Parameters

TemplateAliasParameter:
alias Identifier TemplateAliasParameterSpecializationopt TemplateAliasParameterDefaultopt
alias BasicType Declarator TemplateAliasParameterSpecializationopt TemplateAliasParameterDefaultopt

TemplateAliasParameterSpecialization:
: Type
: ConditionalExpression

TemplateAliasParameterDefault:
= Type
= ConditionalExpression

Alias parameters enable templates to be parameterized with symbol names or values computed at compile-time. Almost any kind of D symbol can be used, including type names, global names, local names, module names, template names, and template instances.

Symbol Aliases

- Type names

class Foo
{
static int x;
}

template Bar(alias a)
{
alias sym = a.x;
}

void main()
{
alias bar = Bar!(Foo);
bar.sym = 3;  // sets Foo.x to 3
assert(Foo.x == 3);
}

- Global names

shared int x;

template Foo(alias var)
{
auto ptr = &var;
}

void main()
{
alias bar = Foo!(x);
*bar.ptr = 3;       // set x to 3
assert(x == 3);

static shared int y;
alias abc = Foo!(y);
*abc.ptr = 3;       // set y to 3
assert(y == 3);
}

- Local names

template Foo(alias var)
{
void inc() { var++; }
}

void main()
{
int v = 4;
alias foo = Foo!v;
foo.inc();
assert(v == 5);
}

See also Implicit Template Nesting.

- Module names

import std.conv;

template Foo(alias a)
{
alias sym = a.text;
}

void main()
{
alias bar = Foo!(std.conv);
string s = bar.sym(3);   // calls std.conv.text(3)
assert(s == "3");
}

- Template names

shared int x;

template Foo(alias var)
{
auto ptr = &var;
}

template Bar(alias Tem)
{
alias instance = Tem!(x);
}

void main()
{
alias bar = Bar!(Foo);
*bar.instance.ptr = 3;  // sets x to 3
assert(x == 3);
}

- Template instance names

shared int x;

template Foo(alias var)
{
auto ptr = &var;
}

template Bar(alias sym)
{
alias p = sym.ptr;
}

void main()
{
alias foo = Foo!(x);
alias bar = Bar!(foo);
*bar.p = 3;  // sets x to 3
assert(x == 3);
}

Value Aliases

- Literals

template Foo(alias x, alias y)
{
static int i = x;
static string s = y;
}

void main()
{
import std.stdio;
alias foo = Foo!(3, "bar");
writeln(foo.i, foo.s);  // prints 3bar
}

- Compile-time values

template Foo(alias x)
{
static int i = x;
}

void main()
{
// compile-time argument evaluation
enum two = 1 + 1;
alias foo = Foo!(5 * two);
assert(foo.i == 10);
static assert(foo.stringof == "Foo!10");

// compile-time function evaluation
int get10() { return 10; }
alias bar = Foo!(get10());
// bar is the same template instance as foo
assert(&bar.i is &foo.i);
}

- Function Literals

template Foo(alias fun)
{
enum val = fun(2);
}

alias foo = Foo!((int x) => x * x);
static assert(foo.val == 4);

Typed Alias Parameters

Alias parameters can also be typed. These parameters will accept symbols of that type:

template Foo(alias int p) { alias a = p; }

void fun()
{
int i = 0;
Foo!i.a++;  // ok
assert(i == 1);

float f;
//Foo!f;  // fails to instantiate
}

Specialization

Alias parameters can accept both literals and user-defined type symbols, but they are less specialized than the matches to type parameters and value parameters:

template Foo(T)         { ... }  // #1
template Foo(int n)     { ... }  // #2
template Foo(alias sym) { ... }  // #3

struct S {}
int var;

alias foo1  = Foo!(S);      // instantiates #1
alias foo2  = Foo!(1);      // instantiates #2
alias foo3a = Foo!([1,2]);  // instantiates #3
alias foo3b = Foo!(var);    // instantiates #3

template Bar(alias A) { ... }                 // #4
template Bar(T : U!V, alias U, V...) { ... }  // #5

class C(T) {}
alias bar = Bar!(C!int);    // instantiates #5

Sequence Parameters

TemplateSequenceParameter:
Identifier ...

If the last template parameter in the TemplateParameterList is declared as a TemplateSequenceParameter, it is a match with zero or more trailing template arguments. Any argument that can be passed to a TemplateAliasParameter can be passed to a sequence parameter.

Such a sequence of arguments can itself be aliased for use outside a template. The std.meta.AliasSeq template simply aliases its sequence parameter:

alias AliasSeq(Args...) = Args;

A TemplateSequenceParameter will thus henceforth be referred to by that name for clarity. An AliasSeq is not itself a type, value, or symbol. It is a compile-time sequence of any mix of types, values or symbols, or none.

The elements of an AliasSeq are automatically expanded when it is referenced in a declaration or expression. An AliasSeq can be used as arguments to instantiate a template.

Homogeneous Sequences

- An AliasSeq whose elements consist entirely of types is called a type sequence or TypeSeq.

- An AliasSeq whose elements consist entirely of values is called a value sequence or ValueSeq.

- typeof can be used on a ValueSeq to obtain a TypeSeq.

A ValueSeq can be used as arguments to call a function:

import std.stdio : writeln;

template print(args...) // args must be a ValueSeq
{
void print()
{
writeln("args are ", args);
}
}

void main()
{
print!(1, 'a', 6.8)(); // prints: args are 1a6.8
}

A TypeSeq can be used to declare a parameter sequence for a function:

import std.stdio : writeln;

template print(Types...) // Types must be a TypeSeq
{
void print(Types args) // args is a ValueSeq
{
writeln("args are ", args);
}
}

void main()
{
print!(int, char, double)(1, 'a', 6.8); // prints: args are 1a6.8
}

A value sequence:

- Can be copied

- Cannot have its address taken

- Cannot be returned from a function - instead, return a std.typecons.Tuple

Lvalue Sequences

A TypeSeq can similarly be used to declare variables. A variable whose type is a TypeSeq is called an lvalue sequence.

- An element of an lvalue sequence may be modifiable.

- An lvalue sequence can be initialized from, assigned to, and compared with a value sequence whose elements are compatible.

import std.meta: AliasSeq;
// use a type alias just for convenience
alias TS = AliasSeq!(string, int);

TS tup; // lvalue sequence
assert(tup == AliasSeq!("", 0)); // TS.init
// elements can be modified
tup[1]++;
assert(tup[1] == 1);

byte i = 5;
// initialize another lvalue sequence from a sequence of a value and a symbol
auto tup2 = AliasSeq!("hi", i); // value of i is copied
i++;
assert(tup2[1] == 5); // unchanged

enum hi5 = AliasSeq!("hi", 5); // rvalue sequence
static assert(is(typeof(hi5) == TS));
// compare elements
assert(tup2 == hi5); // OK, byte and int have a common type

// lvalue sequence can be assigned to a ValueSeq
tup = tup2;
assert(tup == hi5);

- .tupleof can be used on a class or struct instance to obtain an lvalue sequence of its fields.

- .tupleof can be used on a static array instance to obtain an lvalue sequence of its elements.

An lvalue sequence can be initialized from a single expression. Each element is initialized from the given expression.

import std.meta: AliasSeq;
AliasSeq!(int, int, int) vs = 4;
assert(vs == AliasSeq!(4, 4, 4));

int[3] sa = [1, 2, 3];
vs = sa.tupleof;
assert(vs == AliasSeq!(1, 2, 3));

Sequence Operations

- The number of elements in an AliasSeq can be retrieved with the .length property.

- The nth element can be retrieved by indexing an AliasSeq with Seq[n]. Indexes must be known at compile-time. The result is an lvalue when the element is a symbol which resolves to a variable, or when the sequence is an lvalue sequence.

- Slicing produces a new sequence with a subset of the elements of the original sequence.

import std.meta : AliasSeq;

int v = 4;
// alias a sequence of 3 values and one symbol
alias nums = AliasSeq!(1, 2, 3, v);
static assert(nums.length == 4);
static assert(nums[1] == 2);

//nums[0]++; // Error, nums[0] is an rvalue
nums[3]++; // OK, nums[3] is bound to v, an lvalue
assert(v == 5);

// slice first 3 elements
alias trio = nums[0 .. $-1];
// expand into an array literal
static assert([trio] == [1, 2, 3]);

AliasSeqs are static compile-time entities, there is no way to dynamically change, add, or remove elements either at compile-time or run-time. Instead, either:

- Construct a new sequence using the original sequence (or a slice of it) and additional elements before or after it.

- Use Alias Assignment to build new sequences iteratively.

Sequences can 'unroll' code for each element using a foreach statement.

Type Sequence Deduction

Type sequences can be deduced from the trailing parameters of an implicitly instantiated function template:

import std.stdio;

template print(T, Args...)
{
void print(T first, Args args)
{
writeln(first);
static if (args.length) // if more arguments
print(args);        // recurse for remaining arguments
}
}

void main()
{
// Calls `print` with:
// T = int, Args = (char, double)
// T = char, Args = (double)
// T = double, Args = ()
print(1, 'a', 6.8);
}

prints:

1
a
6.8

Type sequences can also be deduced from the parameter list of a function pointer or delegate passed as a function argument:

size_t arity(R, Args...)(R function(Args) fp) => Args.length;

int f(int);
void g(string, Object);

static assert(arity((){}) == 0); // R = void, Args = ()
static assert(arity(&f) == 1);   // R = int, Args = (int)
static assert(arity(&g) == 2);   // R = void, Args = (string, Object)

Deduction can partially match a parameter list:

/* Partially applies a delegate by tying its first argument to a particular value.
* R = return type
* T = first argument type
* Args = TypeSeq of remaining argument types
*/
R delegate(Args) partial(R, T, Args...)(R function(T, Args) dg, T first)
{
// return a closure
return (Args args) => dg(first, args);
}

int plus(int x, int y, int z) => x + y + z;

void main()
{
auto plus_two = partial(&plus, 2); // R = int, T = int, Args = (int, int)
assert(plus_two(6, 8) == 16);
}

See also: std.functional.partial.

Specialization

If both a template with a sequence parameter and a template without a sequence parameter exactly match a template instantiation, the template without a TemplateSequenceParameter is selected.

template Foo(T)         { pragma(msg, "1"); }   // #1
template Foo(int n)     { pragma(msg, "2"); }   // #2
template Foo(alias sym) { pragma(msg, "3"); }   // #3
template Foo(Args...)   { pragma(msg, "4"); }   // #4

import std.stdio;

// Any sole template argument will never match to #4
alias foo1 = Foo!(int);          // instantiates #1
alias foo2 = Foo!(3);            // instantiates #2
alias foo3 = Foo!(std);          // instantiates #3

alias foo4 = Foo!(int, 3, std);  // instantiates #4

Default Arguments

Trailing template parameters can be given default arguments:

template Foo(T, U = int) { ... }
Foo!(uint,long); // instantiate Foo with T as uint, and U as long
Foo!(uint);      // instantiate Foo with T as uint, and U as int

template Foo(T, U = T*) { ... }
Foo!(uint);      // instantiate Foo with T as uint, and U as uint*

See also: Function Template Default Arguments.

Eponymous Templates

If a template contains members whose name is the same as the template identifier then these members are assumed to be referred to in a template instantiation:

template foo(T)
{
T foo; // declare variable foo of type T
}

void main()
{
foo!(int) = 6; // instead of foo!(int).foo
}

The following example has more than one eponymous member and uses Implicit Function Template Instantiation:

template foo(S, T)
{
// each member contains all the template parameters
void foo(S s, T t) {}
void foo(S s, T t, string) {}
}

void main()
{
foo(1, 2, "test"); // foo!(int, int).foo(1, 2, "test")
foo(1, 2); // foo!(int, int).foo(1, 2)
}

Aggregate Type Templates

ClassTemplateDeclaration:
class Identifier TemplateParameters ;
class Identifier TemplateParameters Constraintopt BaseClassListopt AggregateBody
class Identifier TemplateParameters BaseClassListopt Constraintopt AggregateBody

InterfaceTemplateDeclaration:
interface Identifier TemplateParameters ;
interface Identifier TemplateParameters Constraintopt BaseInterfaceListopt AggregateBody
interface Identifier TemplateParameters BaseInterfaceList Constraint AggregateBody

StructTemplateDeclaration:
struct Identifier TemplateParameters ;
struct Identifier TemplateParameters Constraintopt AggregateBody

UnionTemplateDeclaration:
union Identifier TemplateParameters ;
union Identifier TemplateParameters Constraintopt AggregateBody

If a template declares exactly one member, and that member is a class with the same name as the template (see Eponymous Templates:)

template Bar(T)
{
class Bar
{
T member;
}
}

then the semantic equivalent, called a ClassTemplateDeclaration can be written as:

class Bar(T)
{
T member;
}

See also: This Parameters.

Analogously to class templates, struct, union and interfaces can be transformed into templates by supplying a template parameter list.

Function Templates

If a template declares exactly one member, and that member is a function with the same name as the template, it is a function template declaration. Alternatively, a function template declaration is a function declaration with a TemplateParameterList immediately preceding the Parameters.

A function template to compute the square of type T is:

T square(T)(T t)
{
return t * t;
}

It is lowered to:

template square(T)
{
T square(T t)
{
return t * t;
}
}

Function templates can be explicitly instantiated with Identifier!(TemplateArgumentList):

writefln("The square of %s is %s", 3, square!(int)(3));

Implicit Function Template Instantiation (IFTI)

Function templates can be implicitly instantiated if the TemplateArgumentList is deducible from the types of the function arguments:

T square(T)(T t)
{
return t * t;
}

writefln("The square of %s is %s", 3, square(3));  // T is deduced to be int

Type parameter deduction is not influenced by the order of function arguments.

If there are fewer arguments supplied in the TemplateArgumentList than parameters in the TemplateParameterList, the arguments fill parameters from left to right, and the rest of the parameters are then deduced from the function arguments.

Restrictions

Function template type parameters that are to be implicitly deduced must appear in the type of at least one function parameter:

void foo(T : U*, U)(U t) {}

void main()
{
int x;
foo!(int*)(x);   // ok, U is deduced and T is specified explicitly
//foo(x);        // error, only U can be deduced, not T
}

When the template parameters must be deduced, the eponymous members can't rely on a static if condition since the deduction relies on how the members are used:

template foo(T)
{
static if (is(T)) // T is not yet known...
void foo(T t) {} // T is deduced from the member usage
}

void main()
{
//foo(0); // Error: cannot deduce function from argument types
foo!int(0); // Ok since no deduction necessary
}

IFTI does not work when the parameter type is an alias template instance:

struct S(T) {}
alias A(T) = S!T;
void f(T)(A!T) {}

void main()
{
A!int v;
//f(v); // error
f!int(v); // OK
}

A TypeSeq template parameter can be deduced from function arguments.

Type Conversions

If template type parameters match the literal expressions on function arguments, the deduced types may consider narrowing conversions of them.

void foo(T)(T v)        { pragma(msg, "in foo, T = ", T); }
void bar(T)(T v, T[] a) { pragma(msg, "in bar, T = ", T); }

void main()
{
foo(1);
// an integer literal type is analyzed as int by default
// then T is deduced to int

short[] arr;
bar(1, arr);
// arr is short[], and the integer literal 1 is
// implicitly convertible to short.
// then T will be deduced to short.

bar(1, [2.0, 3.0]);
// the array literal is analyzed as double[],
// and the integer literal 1 is implicitly convertible to double.
// then T will be deduced to double.
}

The deduced type parameter for dynamic array and pointer arguments has an unqualified head:

void foo(T)(T arg) { pragma(msg, T); }

void test()
{
int[] marr;
const(int[]) carr;
immutable(int[]) iarr;
foo(marr);  // T == int[]
foo(carr);  // T == const(int)[]
foo(iarr);  // T == immutable(int)[]

int* mptr;
const(int*) cptr;
immutable(int*) iptr;
foo(mptr);  // T == int*
foo(cptr);  // T == const(int)*
foo(iptr);  // T == immutable(int)*
}

Return Type Deduction

Function templates can have their return types deduced based on the ReturnStatements in the function, just as with normal functions. See Auto Functions.

auto square(T)(T t)
{
return t * t;
}

auto i = square(2);
static assert(is(typeof(i) == int));

Auto Ref Parameters

Template functions can have auto ref parameters. An auto ref parameter becomes a ref parameter if its corresponding argument is an lvalue, otherwise it becomes a value parameter:

int countRefs(Args...)(auto ref Args args)
{
int result;

foreach (i, _; args)
{
if (__traits(isRef, args[i]))
result++;
}
return result;
}

void main()
{
int y;
assert(countRefs(3, 4) == 0);
assert(countRefs(3, y, 4) == 1);
assert(countRefs(y, 6, y) == 2);
}

Auto ref parameters can be combined with auto ref return attributes:

auto ref min(T, U)(auto ref T lhs, auto ref U rhs)
{
return lhs > rhs ? rhs : lhs;
}

void main()
{
int i;
i = min(4, 3);
assert(i == 3);

int x = 7, y = 8;
i = min(x, y);
assert(i == 7);
// result is an lvalue
min(x, y) = 10;    // sets x to 10
assert(x == 10 && y == 8);

static assert(!__traits(compiles, min(3, y) = 10));
static assert(!__traits(compiles, min(y, 3) = 10));
}

Default Arguments

Template arguments not implicitly deduced can have default values:

void foo(T, U=T*)(T t) { U p; ... }

int x;
foo(x);    // T is int, U is int*

Variadic Function Templates can have parameters with default values. These parameters are always set to their default value in case of IFTI.

size_t fun(T...)(T t, string file = __FILE__)
{
import std.stdio;
writeln(file, " ", t);
return T.length;
}

assert(fun(1, "foo") == 2);  // uses IFTI
assert(fun!int(1, "filename") == 1);  // no IFTI

Template Constructors

ConstructorTemplate:
this TemplateParameters Parameters MemberFunctionAttributesopt Constraintopt FunctionBody
this TemplateParameters Parameters MemberFunctionAttributesopt Constraintopt MissingFunctionBody

Templates can be used to form constructors for classes and structs.

Enum & Variable Templates

Like aggregates and functions, variable declarations and manifest constants can have template parameters, providing there is an Initializer:

enum bool within(alias v, T) = v <= T.max && v >= T.min;
ubyte[T.sizeof] storage(T) = 0;
const triplet(alias v) = [v, v+1, v+2];

static assert(within!(-128F, byte));
static assert(storage!(int[2]).length == 8);
static assert(triplet!3 == [3, 4, 5]);

Those declarations are transformed into these TemplateDeclarations:

template within(alias v, T)
{
enum bool within = v <= T.max && v >= T.min;
}
template storage(T)
{
ubyte[T.sizeof] storage = 0;
}
template triplet(alias v)
{
const triplet = [v, v+1, v+2];
}

Alias Templates

AliasDeclaration can also have optional template parameters:

alias ElementType(T : T[]) = T;
alias Sequence(TL...) = TL;

It is lowered to:

template ElementType(T : T[])
{
alias ElementType = T;
}
template Sequence(TL...)
{
alias Sequence = TL;
}

Nested Templates

If a template is declared in aggregate or function local scope, the instantiated functions will implicitly capture the context of the enclosing scope.

class C
{
int num;

this(int n) { num = n; }

template Foo()
{
// 'foo' can access 'this' reference of class C object.
void foo(int n) { this.num = n; }
}
}

void main()
{
auto c = new C(1);
assert(c.num == 1);

c.Foo!().foo(5);
assert(c.num == 5);

template Bar()
{
// 'bar' can access local variable of 'main' function.
void bar(int n) { c.num = n; }
}
Bar!().bar(10);
assert(c.num == 10);
}

Above, Foo!().foo will work just the same as a final member function of class C, and Bar!().bar will work just the same as a nested function within function main().

Aggregate Type Limitations

A nested template cannot add non-static fields to an aggregate type. Fields declared in a nested template will be implicitly static.

A nested template cannot add virtual functions to a class or interface. Methods inside a nested template will be implicitly final.

class Foo
{
template TBar(T)
{
T xx;           // becomes a static field of Foo
void func(T) {} // implicitly final
//abstract void baz(); // error, final functions cannot be abstract

static T yy;                    // Ok
static void func(T t, int y) {} // Ok
}
}

void main()
{
alias bar = Foo.TBar!int;
bar.xx++;
//bar.func(1); // error, no this

auto o = new Foo;
o.TBar!int.func(1); // OK
}

Implicit Nesting

If a template has a template alias parameter, and is instantiated with a local symbol, the instantiated function will implicitly become nested in order to access runtime data of the given local symbol.

template Foo(alias sym)
{
void foo() { sym = 10; }
}

class C
{
int num;

this(int n) { num = n; }

void main()
{
assert(this.num == 1);

alias fooX = Foo!(C.num).foo;

// fooX will become member function implicitly, so &fooX
//     returns a delegate object.
static assert(is(typeof(&fooX) == delegate));

fooX(); // called by using valid 'this' reference.
assert(this.num == 10);  // OK
}
}

void main()
{
new C(1).main();

int num;
alias fooX = Foo!num.foo;

// fooX will become nested function implicitly, so &fooX
//     returns a delegate object.
static assert(is(typeof(&fooX) == delegate));

fooX();
assert(num == 10);  // OK
}

Not only functions, but also instantiated class and struct types can become nested via implicitly captured context.

class C
{
int num;
this(int n) { num = n; }

class N(T)
{
// instantiated class N!T can become nested in C
T foo() { return num * 2; }
}
}

void main()
{
auto c = new C(10);
auto n = c.new N!int();
assert(n.foo() == 20);
}

See also: Nested Class Instantiation.

void main()
{
int num = 10;
struct S(T)
{
// instantiated struct S!T can become nested in main()
T foo() { return num * 2; }
}
S!int s;
assert(s.foo() == 20);
}

A templated struct can become a nested struct if it is instantiated with a local symbol passed as an aliased argument:

struct A(alias F)
{
int fun(int i) { return F(i); }
}

A!F makeA(alias F)() { return A!F(); }

void main()
{
int x = 40;
int fun(int i) { return x + i; }
A!fun a = makeA!fun();
assert(a.fun(2) == 42);
}

Context Limitation

Currently nested templates can capture at most one context. As a typical example, non-static template member functions cannot take local symbol by using template alias parameter.

class C
{
int num;
void foo(alias sym)() { num = sym * 2; }
}

void main()
{
auto c = new C();
int var = 10;
c.foo!var();    // NG, foo!var requires two contexts, 'this' and 'main()'
}

But, if one context is indirectly accessible from other context, it is allowed.

int sum(alias x, alias y)() { return x + y; }

void main()
{
int a = 10;
void nested()
{
int b = 20;
assert(sum!(a, b)() == 30);
}
nested();
}

Two local variables a and b are in different contexts, but outer context is indirectly accessible from innter context, so nested template instance sum!(a, b) will capture only inner context.

Recursive Templates

Template features can be combined to produce some interesting effects, such as compile time evaluation of non-trivial functions. For example, a factorial template can be written:

template factorial(int n)
{
static if (n == 1)
enum factorial = 1;
else
enum factorial = n * factorial!(n - 1);
}

static assert(factorial!(4) == 24);

For more information and a CTFE (Compile-time Function Execution) factorial alternative, see: Template Recursion.

Template Constraints

Constraint:
if ( Expression )

Constraints are used to impose additional constraints on matching arguments to a template beyond what is possible in the TemplateParameterList. The Expression is computed at compile time and returns a result that is converted to a boolean value. If that value is true, then the template is matched, otherwise the template is not matched.

For example, the following function template only matches with odd values of N:

void foo(int N)()
if (N & 1)
{
...
}
...
foo!(3)();  // OK, matches
foo!(4)();  // Error, no match

Template constraints can be used with aggregate types (structs, classes, unions). Constraints are effectively used with library module std.traits:

import std.traits;

struct Bar(T)
if (isIntegral!T)
{
...
}
...
auto x = Bar!int;       // OK, int is an integral type
auto y = Bar!double;    // Error, double does not satisfy constraint

)

Template Mixins

A TemplateMixin takes an arbitrary set of declarations from the body of a TemplateDeclaration and inserts them into the current context.

TemplateMixinDeclaration:
mixin template Identifier TemplateParameters Constraintopt { DeclDefsopt }

TemplateMixin:
mixin MixinTemplateName TemplateArgumentsopt Identifieropt ;
mixin Identifier = MixinTemplateName TemplateArgumentsopt ;

MixinTemplateName:
. MixinQualifiedIdentifier
MixinQualifiedIdentifier
Typeof . MixinQualifiedIdentifier

MixinQualifiedIdentifier:
Identifier
Identifier . MixinQualifiedIdentifier
TemplateInstance . MixinQualifiedIdentifier

A TemplateMixin can occur in declaration lists of modules, classes, structs, unions, or as a statement. MixinTemplateName must refer to a TemplateDeclaration or TemplateMixinDeclaration. If the TemplateDeclaration requires no parameters, TemplateArguments can be omitted.

Unlike a template instantiation, a template mixin's body is evaluated within the scope where the mixin appears, not where the template declaration is defined. It is analogous to cutting and pasting the body of the template into the location of the mixin into a nested scope. It is useful for injecting parameterized ‘boilerplate’ code, as well as for creating templated nested functions, which is not always possible with template instantiations.

A TemplateMixinDeclaration is the same as a TemplateDeclaration, but can not be instantiated outside of a TemplateMixin.

int y = 3;

mixin template Foo()
{
int abc() { return y; }
}

void test()
{
int y = 8;
mixin Foo; // local y is picked up, not global y
assert(abc() == 8);
}

import std.stdio : writeln;

mixin template Foo()
{
int x = 5;
}

mixin Foo;

struct Bar
{
mixin Foo;
}

void main()
{
writeln("x = ", x);         // prints 5
{
Bar b;
int x = 3;

writeln("b.x = ", b.x); // prints 5
writeln("x = ", x);     // prints 3
{
mixin Foo;
writeln("x = ", x); // prints 5
x = 4;
writeln("x = ", x); // prints 4
}
writeln("x = ", x);     // prints 3
}
writeln("x = ", x);         // prints 5
}

Mixin Parameters

Mixins can be parameterized:

mixin template Foo(T)
{
T x = 5;
}

mixin Foo!(int);           // create x of type int

Mixins can parameterize symbols using alias parameters:

mixin template Foo(alias b)
{
int abc() { return b; }
}

void test()
{
int y = 8;
mixin Foo!(y);
assert(abc() == 8);
}

Example

This example uses a mixin to implement a generic Duff's device for an arbitrary statement (in this case, the arbitrary statement is in bold). A nested function is generated as well as a delegate literal, these can be inlined by the compiler:

import std.stdio : writeln;

mixin template duffs_device(alias low, alias high, alias fun)
{
void duff_loop()
{
if (low < high)
{
auto n = (high - low + 7) / 8;
switch ((high - low) % 8)
{
case 0: do { fun(); goto case;
case 7:      fun(); goto case;
case 6:      fun(); goto case;
case 5:      fun(); goto case;
case 4:      fun(); goto case;
case 3:      fun(); goto case;
case 2:      fun(); goto case;
case 1:      fun(); continue;
default:     assert(0, "Impossible");
} while (--n > 0);
}
}
}
}

void main()
{
int i = 1;
int j = 11;

mixin duffs_device!(i, j, delegate { writeln("foo"); });
duff_loop();  // executes foo() 10 times
}

Mixin Scope

The declarations in a mixin are placed in a nested scope and then ‘imported’ into the surrounding scope. If the name of a declaration in a mixin is the same as a declaration in the surrounding scope, the surrounding declaration overrides the mixin one:

import std.stdio : writeln;

int x = 3;

mixin template Foo()
{
int x = 5;
int y = 5;
}

mixin Foo;
int y = 3;

void main()
{
writeln("x = ", x);  // prints 3
writeln("y = ", y);  // prints 3
}

A mixin has its own scope, even if a declaration is overridden by the enclosing one:

import std.stdio : writeln;

int x = 4;

mixin template Foo()
{
int x = 5;
int bar() { return x; }
}

mixin Foo;

void main()
{
writeln("x = ", x);         // prints 4
writeln("bar() = ", bar()); // prints 5
}

If two different mixins are put in the same scope, and each define a declaration with the same name, there is an ambiguity error when the declaration is referenced:

import std.stdio : writeln;

mixin template Foo()
{
int x = 5;
void func(int x) { }
}

mixin template Bar()
{
int x = 4;
void func(long x) { }
}

mixin Foo;
mixin Bar;

void main()
{
writeln("x = ", x);    // error, x is ambiguous
func(1);               // error, func is ambiguous
}

The call to func() is ambiguous because Foo.func and Bar.func are in different scopes.

Resolving Ambiguities

If a mixin has an Identifier, it can be used to disambiguate between conflicting symbols:

import std.stdio : writeln;

int x = 6;

mixin template Foo()
{
int x = 5;
int y = 7;
void func() { }
}

mixin template Bar()
{
int x = 4;
void func() { }
}

mixin Foo F;
mixin Bar B;

void main()
{
writeln("y = ", y);     // prints 7
writeln("x = ", x);     // prints 6
writeln("F.x = ", F.x); // prints 5
writeln("B.x = ", B.x); // prints 4
F.func();                  // calls Foo.func
B.func();                  // calls Bar.func
}

Alias declarations can be used to form an overload set of functions declared in different mixins:

mixin template Foo()
{
void func(int x) {  }
}

mixin template Bar()
{
void func(long x) {  }
}

mixin Foo!() F;
mixin Bar!() B;

alias func = F.func;
alias func = B.func;

void main()
{
func(1);  // calls B.func
func(1L); // calls F.func
}

Aggregate Type Mixins

Mixin Virtual Functions

Mixins can add virtual functions to a class:

import std.stdio : writeln;

mixin template Foo()
{
void func() { writeln("Foo.func()"); }
}

class Bar
{
mixin Foo;
}

class Code : Bar
{
override void func() { writeln("Code.func()"); }
}

void main()
{
Bar b = new Bar();
b.func();      // calls Foo.func()

b = new Code();
b.func();      // calls Code.func()
}

Mixin Destructors

An aggregate type can mixin additional destructors. Destructors are run in the opposite order to declaration order.

import std.stdio;

mixin template addNewDtor()
{
~this()
{
writeln("Mixin dtor");
}
}

struct S
{
~this()
{
writeln("Struct dtor");
}

mixin addNewDtor;
}

void main()
{
S s;
// prints `Mixin dtor`
// prints `Struct dtor`
}

Contract Programming

Contracts enable specifying conditions that must hold true when the flow of runtime execution reaches the contract. If a contract is not true, then the program is assumed to have entered an undefined state.

Rationale:

Building contract support into the language provides:

1. a consistent look and feel for the contracts

2. tool support

3. the implementation can generate better code using information gathered from the contracts

4. easier management and enforcement of contracts

5. handling of contract inheritance

Assert Contract

See AssertExpression.

Pre and Post Contracts

See:

- in contracts.

- out contracts.

Invariants

See Struct Invariants and Class Invariants.

References

- Contracts Reading List

- Adding Contracts to Java

Conditional Compilation

Conditional compilation is the process of selecting which code to compile and which code to not compile.

ConditionalDeclaration:
Condition DeclarationBlock
Condition DeclarationBlock else DeclarationBlock
Condition : DeclDefsopt
Condition DeclarationBlock else : DeclDefsopt

ConditionalStatement:
Condition NoScopeNonEmptyStatement
Condition NoScopeNonEmptyStatement else NoScopeNonEmptyStatement

If the Condition is satisfied, then the following DeclarationBlock or Statement is compiled in. If it is not satisfied, the DeclarationBlock or Statement after the optional else is compiled in.

Any DeclarationBlock or Statement that is not compiled in still must be syntactically correct.

No new scope is introduced, even if the DeclarationBlock or Statement is enclosed by { }.

ConditionalDeclarations and ConditionalStatements can be nested.

The StaticAssert can be used to issue errors at compilation time for branches of the conditional compilation that are errors.

Condition comes in the following forms:

Condition:
VersionCondition
DebugCondition
StaticIfCondition

Version Condition

VersionCondition:
version ( Identifier )
version ( unittest )
version ( assert )

Versions enable multiple versions of a module to be implemented with a single source file.

The VersionCondition is satisfied if Identifier matches a version identifier.

The version identifier can be set on the command line by the -version switch or in the module itself with a VersionSpecification, or they can be predefined by the compiler.

Version identifiers are in their own unique name space, they do not conflict with debug identifiers or other symbols in the module. Version identifiers defined in one module have no influence over other imported modules.

int k;
version (Demo) // compile in this code block for the demo version
{
int i;
int k;    // error, k already defined

i = 3;
}
x = i;      // uses the i declared above

version (X86)
{
... // implement custom inline assembler version
}
else
{
... // use default, but slow, version
}

The version(unittest) is satisfied if and only if the code is compiled with unit tests enabled (the -unittest option on dmd).

Version Specification

VersionSpecification:
version = Identifier ;

The version specification makes it straightforward to group a set of features under one major version, for example:

version (ProfessionalEdition)
{
version = FeatureA;
version = FeatureB;
version = FeatureC;
}
version (HomeEdition)
{
version = FeatureA;
}
...
version (FeatureB)
{
... implement Feature B ...
}

Version identifiers or levels may not be forward referenced:

version (Foo)
{
int x;
}
version = Foo;  // error, Foo already used

VersionSpecifications may only appear at module scope.

While the debug and version conditions superficially behave the same, they are intended for very different purposes. Debug statements are for adding debug code that is removed for the release version. Version statements are to aid in portability and multiple release versions.

Here's an example of a full version as opposed to a demo version:

class Foo
{
int a, b;

version(full)
{
int extrafunctionality()
{
...
return 1;  // extra functionality is supported
}
}
else // demo
{
int extrafunctionality()
{
return 0;  // extra functionality is not supported
}
}
}

Various different version builds can be built with a parameter to version:

version(identifier) // add in version code if version
// keyword is identifier
{
... version code ...
}

This is presumably set by the command line as -version=identifier.

Predefined Versions

Several environmental version identifiers and identifier name spaces are predefined for consistent usage. Version identifiers do not conflict with other identifiers in the code, they are in a separate name space. Predefined version identifiers are global, i.e. they apply to all modules being compiled and imported.

Predefined Version Identifiers

Version Identifier

Description

Host Compiler

DigitalMars

DMD (Digital Mars D)

GNU

GDC (GNU D Compiler)

LDC

LDC (LLVM D Compiler)

SDC

SDC (Snazzy D Compiler)

Target Operating System

Windows

Microsoft Windows systems Win32 and Win64

Win32

Microsoft 32-bit Windows systems

Win64

Microsoft 64-bit Windows systems

linux

All Linux systems

Apple

Apple systems OSX, iOS, TVOS, WatchOS and VisionOS

OSX

macOS

iOS

iOS

TVOS

tvOS

WatchOS

watchOS

VisionOS

visionOS

FreeBSD

FreeBSD

OpenBSD

OpenBSD

NetBSD

NetBSD

DragonFlyBSD

DragonFlyBSD

BSD

All other BSDs

Solaris

Solaris

Posix

All POSIX systems (includes Linux, FreeBSD, OS X, Solaris, etc.)

AIX

IBM Advanced Interactive eXecutive OS

Haiku

The Haiku operating system

SkyOS

The SkyOS operating system

SysV3

System V Release 3

SysV4

System V Release 4

Hurd

GNU Hurd

Android

The Android platform

Emscripten

The Emscripten platform

PlayStation

The PlayStation platform

PlayStation4

The PlayStation 4 platform

FreeStanding

An environment without an operating system (such as Bare-metal targets)

Target Environment

Cygwin

The Cygwin environment

MinGW

The MinGW environment

CRuntime_Bionic

Bionic C runtime

CRuntime_DigitalMars

DigitalMars C runtime

CRuntime_Glibc

Glibc C runtime

CRuntime_Microsoft

Microsoft C runtime

CRuntime_Musl

musl C runtime

CRuntime_Newlib

newlib C runtime

CRuntime_UClibc

uClibc C runtime

CRuntime_WASI

WASI C runtime

CppRuntime_Clang

Deprecated, use CppRuntime_LLVM instead

CppRuntime_DigitalMars

DigitalMars C++ runtime

CppRuntime_Gcc

Deprecated, use CppRuntime_GNU instead

CppRuntime_LLVM

LLVM libc++ C++ runtime

CppRuntime_GNU

GNU libstdc++ C++ runtime

CppRuntime_Microsoft

Microsoft C++ runtime

CppRuntime_Sun

Sun C++ runtime

Target CPU and Instruction Set

X86

Intel and AMD 32-bit processors

X86_64

Intel and AMD 64-bit processors

ARM

The ARM architecture (32-bit) (AArch32 et al)

ARM_Thumb

ARM in any Thumb mode

ARM_SoftFloat

The ARM soft floating point ABI

ARM_SoftFP

The ARM softfp floating point ABI

ARM_HardFloat

The ARM hardfp floating point ABI

AArch64

The Advanced RISC Machine architecture (64-bit)

AsmJS

The asm.js intermediate programming language

AVR

8-bit Atmel AVR Microcontrollers

Epiphany

The Epiphany architecture

PPC

The PowerPC architecture, 32-bit

PPC_SoftFloat

The PowerPC soft float ABI

PPC_HardFloat

The PowerPC hard float ABI

PPC64

The PowerPC architecture, 64-bit

IA64

The Itanium architecture (64-bit)

MIPS32

The MIPS architecture, 32-bit

MIPS64

The MIPS architecture, 64-bit

MIPS_O32

The MIPS O32 ABI

MIPS_N32

The MIPS N32 ABI

MIPS_O64

The MIPS O64 ABI

MIPS_N64

The MIPS N64 ABI

MIPS_EABI

The MIPS EABI

MIPS_SoftFloat

The MIPS soft-float ABI

MIPS_HardFloat

The MIPS hard-float ABI

MSP430

The MSP430 architecture

NVPTX

The Nvidia Parallel Thread Execution (PTX) architecture, 32-bit

NVPTX64

The Nvidia Parallel Thread Execution (PTX) architecture, 64-bit

RISCV32

The RISC-V architecture, 32-bit

RISCV64

The RISC-V architecture, 64-bit

SPARC

The SPARC architecture, 32-bit

SPARC_V8Plus

The SPARC v8+ ABI

SPARC_SoftFloat

The SPARC soft float ABI

SPARC_HardFloat

The SPARC hard float ABI

SPARC64

The SPARC architecture, 64-bit

S390

The System/390 architecture, 32-bit

SystemZ

The System Z architecture, 64-bit

HPPA

The HP PA-RISC architecture, 32-bit

HPPA64

The HP PA-RISC architecture, 64-bit

SH

The SuperH architecture, 32-bit

WebAssembly

The WebAssembly virtual ISA (instruction set architecture), 32-bit

WASI

The WebAssembly System Interface

Xtensa

The Xtensa Architecture, 32-bit

Alpha

The Alpha architecture

Alpha_SoftFloat

The Alpha soft float ABI

Alpha_HardFloat

The Alpha hard float ABI

Byte Order (endianess)

LittleEndian

Byte order, least significant first

BigEndian

Byte order, most significant first

Executable and Link Format

ELFv1

Elf version 1

ELFv2

Elf version 2

Miscellaneous

D_BetterC

D as Better C code (command line switch -betterC) is being generated

D_Exceptions

Exception handling is supported. Evaluates to false when compiling with command line switch -betterC

D_ModuleInfo

ModuleInfo is supported. Evaluates to false when compiling with command line switch -betterC

D_TypeInfo

Runtime type information (a.k.a TypeInfo) is supported. Evaluates to false when compiling with command line switch -betterC

D_Coverage

Code coverage analysis instrumentation (command line switch -cov) is being generated

D_Ddoc

Ddoc documentation (command line switch -D) is being generated

D_InlineAsm_X86

Inline assembler for X86 is implemented

D_InlineAsm_X86_64

Inline assembler for X86-64 is implemented

D_LP64

Pointers are 64 bits (command line switch -m64). (Do not confuse this with C's LP64 model)

D_X32

Pointers are 32 bits, but words are still 64 bits (x32 ABI) (This can be defined in parallel to X86_64)

D_HardFloat

The target hardware has a floating-point unit

D_SoftFloat

The target hardware does not have a floating-point unit

D_PIC

Position Independent Code (command line switch -fPIC) is being generated

D_PIE

Position Independent Executable (command line switch -fPIE) is being generated

D_SIMD

Vector extensions (via __simd) are supported

D_AVX

AVX Vector instructions are supported

D_AVX2

AVX2 Vector instructions are supported

D_Version2

This is a D version 2 compiler

D_NoBoundsChecks

Array bounds checks are disabled (command line switch -boundscheck=off)

D_ObjectiveC

The target supports interfacing with Objective-C

D_ProfileGC

GC allocations being profiled (command line switch -profile=gc)

D_Optimized

Compiling with enabled optimizations (command line switch -O)

Core

Defined when building the standard runtime

Std

Defined when building the standard library

unittest

Unit tests are enabled (command line switch -unittest)

assert

Checks are being emitted for AssertExpressions

D_PreConditions

Checks are being emitted for in contracts

D_PostConditions

Checks are being emitted for out contracts

D_Invariants

Checks are being emitted for class invariants and struct invariants

Special Cases

none

Never defined; used to just disable a section of code

all

Always defined; used as the opposite of none

The following identifiers are defined, but are deprecated:

Predefined Version Identifiers (deprecated)

Version Identifier

Description

darwin

The Darwin operating system; use OSX instead

Thumb

ARM in Thumb mode; use ARM_Thumb instead

S390X

The System/390X architecture, 64-bit; use SystemZ instead

Others will be added as they make sense and new implementations appear.

To allow for future growth of the language, the version identifier namespace beginning with "D_" is reserved for identifiers indicating D language specification or new feature conformance. Further, all identifiers derived from the ones listed above by appending any character(s) are reserved. This means that e.g. ARM_foo and Windows_bar are reserved while foo_ARM and bar_Windows are not.

Predefined version identifiers from this list cannot be set from the command line or from version statements. (This prevents things like both Windows and linux being simultaneously set.)

Compiler vendor specific versions can be predefined if the trademarked vendor identifier prefixes it, as in:

version(DigitalMars_funky_extension)
{
...
}

It is important to use the right version identifier for the right purpose. For example, use the vendor identifier when using a vendor specific feature. Use the operating system identifier when using an operating system specific feature, etc.

Debug Condition

DebugCondition:
debug
debug ( Identifier )

Two versions of programs are commonly built, a release build and a debug build. The debug build includes extra error checking code, test harnesses, pretty-printing code, etc. The debug statement conditionally compiles in its statement body. It is D's way of what in C is done with #ifdef DEBUG / #endif pairs.

The debug condition is satisfied when the -debug switch is passed to the compiler.

The debug ( Identifier ) condition is satisfied when the debug identifier matches Identifier.

class Foo
{
int a, b;
debug:
int flag;
}

Debug Statement

A ConditionalStatement that has a DebugCondition is called a DebugStatement. DebugStatements have relaxed semantic checks in that pure, @nogc, nothrow and @safe checks are not done. Neither do DebugStatements influence the inference of pure, @nogc, nothrow and @safe attributes.

Undefined Behavior: Since these checks are bypassed, it is up to the programmer to ensure the code is correct. For example, throwing an exception in a nothrow function is undefined behavior.

Best Practices: This enables the easy insertion of code to provide debugging help, by bypassing the otherwise stringent attribute checks. Never ship release code that has DebugStatements enabled.

Debug Specification

DebugSpecification:
debug = Identifier ;

Debug identifiers are set either by the command line switch -debug or by a DebugSpecification.

Debug specifications only affect the module they appear in, they do not affect any imported modules. Debug identifiers are in their own namespace, independent from version identifiers and other symbols.

It is illegal to forward reference a debug specification:

debug(foo) writeln("Foo");
debug = foo;    // error, foo used before set

DebugSpecifications may only appear at module scope.

Various different debug builds can be built with a parameter to debug:

debug(identifier) { } // add in debug code if debug keyword is identifier

These are presumably set by the command line as and -debug=identifier.

Static If Condition

StaticIfCondition:
static if ( AssignExpression )

AssignExpression is implicitly converted to a boolean type, and is evaluated at compile time. The condition is satisfied if it evaluates to true. It is not satisfied if it evaluates to false.

It is an error if AssignExpression cannot be implicitly converted to a boolean type or if it cannot be evaluated at compile time.

StaticIfConditions can appear in module, class, template, struct, union, or function scope. In function scope, the symbols referred to in the AssignExpression can be any that can normally be referenced by an expression at that point.

const int i = 3;
int j = 4;

static if (i == 3)    // ok, at module scope
int x;

class C
{
const int k = 5;

static if (i == 3) // ok
int x;
else
long x;

static if (j == 3) // error, j is not a constant
int y;

static if (k == 5) // ok, k is in current scope
int z;
}

template Int(int i)
{
static if (i == 32)
alias Int = int;
else static if (i == 16)
alias Int = short;
else
static assert(0); // not supported
}

Int!(32) a;  // a is an int
Int!(16) b;  // b is a short
Int!(17) c;  // error, static assert trips

A StaticIfCondition differs from an IfStatement in the following ways:

1. It can be used to conditionally compile declarations, not just statements.

2. It does not introduce a new scope even if { } are used for conditionally compiled statements.

3. For unsatisfied conditions, the conditionally compiled code need only be syntactically correct. It does not have to be semantically correct.

4. It must be evaluatable at compile time.

Static Foreach

StaticForeachDeclaration:
StaticForeach DeclarationBlock
StaticForeach : DeclDefsopt

StaticForeachStatement:
StaticForeach NoScopeNonEmptyStatement

StaticForeach:
static AggregateForeach
static RangeForeach

static foreach expands its DeclarationBlock or DeclDefs into a series of declarations, each of which may reference any ForeachType symbols declared.

- The aggregate/range bounds are evaluated at compile time and turned into a sequence of compile-time entities by evaluating corresponding code with a ForeachStatement/ForeachRangeStatement at compile time.

- The body of the static foreach is then copied a number of times that corresponds to the number of elements of the sequence.

- Within the i-th copy, the name of the static foreach element 'variable' is bound to the i-th entry of the sequence, either as an enum variable declaration (for constants) or an alias declaration (for symbols). (In particular, static foreach variables are never runtime variables.)

static foreach (i; [0, 1, 2, 3])
{
pragma(msg, i);
}

static foreach supports multiple ForeachType variables in cases where the corresponding foreach statement supports them. (In this case, static foreach generates a compile-time sequence of tuples, and the tuples are subsequently unpacked during iteration).

static foreach (i, v; ['a', 'b', 'c', 'd'])
{
static assert(i + 'a' == v);
}

Like bodies of ConditionalDeclarations, a static foreach body does not introduce a new scope. Therefore, it can be used to add declarations to an existing scope:

import std.range : iota;

static foreach (i; iota(0, 3))
{
mixin(`enum x`, i, ` = i;`);
}

pragma(msg, x0, " ", x1," ", x2); // 0 1 2

Inside a function, if a new scope is desired for each expansion, use another set of braces:

void fun()
{
static foreach (s; ["hi", "hey", "hello"])
{{
enum len = s.length;    // local to each iteration
static assert(len <= 5);
}}

static assert(!is(typeof(len)));
}

static foreach supports sequence expansion like foreach.

break and continue

As static foreach is a code generation construct and not a loop, break and continue cannot be used to change control flow within it. Instead of breaking or continuing a suitable enclosing statement, such an usage yields an error (this is to prevent misunderstandings).

int test(int x)
{
int r = -1;
switch(x)
{
static foreach (i; 0 .. 5)
{
case i:
r = i;
break; // error
}
default: break;
}
return r;
}

static foreach (i; 0 .. 10)
{
static assert(test(i) == (i < 5 ? i : -1));
}

An explicit break/continue label can be used to avoid this limitation. (Note that static foreach itself cannot be broken nor continued even if it is explicitly labeled.)

int test(int x)
{
int r = -1;
Lswitch: switch(x)
{
static foreach (i; 0 .. 5)
{
case i:
r = i;
break Lswitch;
}
default: break;
}
return r;
}

static foreach (i; 0 .. 10)
{
static assert(test(i) == (i < 5 ? i : -1));
}

Static Assert

StaticAssert:
static assert ( ArgumentList ) ;

The first AssignExpression is evaluated at compile time, and converted to a boolean value. If the value is true, the static assert is ignored. If the value is false, an error diagnostic is issued and the compile fails.

On failure, any subsequent AssignExpressions will each be converted to string and then concatenated. The resulting string will be printed out along with the error diagnostic.

Unlike AssertExpressions, StaticAsserts are always checked and evaluated by the compiler unless they appear in an unsatisfied conditional.

void foo()
{
if (0)
{
assert(0);  // never trips
static assert(0); // always trips
}
version (BAR)
{
}
else
{
static assert(0); // trips when version BAR is not defined
}
}

StaticAssert is useful tool for drawing attention to conditional configurations not supported in the code.

Traits

Grammar

Traits are extensions to the language to enable programs, at compile time, to get at information internal to the compiler. This is also known as compile time reflection. It is done as a special, easily extended syntax (similar to Pragmas) so that new capabilities can be added as required.

TraitsExpression:
__traits ( TraitsKeyword , TraitsArguments )

TraitsKeyword:
isAbstractClass
isArithmetic
isAssociativeArray
isFinalClass
isPOD
isNested
isFuture
isDeprecated
isFloating
isIntegral
isScalar
isStaticArray
isUnsigned
isDisabled
isVirtualFunction
isVirtualMethod
isAbstractFunction
isFinalFunction
isStaticFunction
isOverrideFunction
isTemplate
isRef
isOut
isLazy
isReturnOnStack
isCopyable
isZeroInit
isModule
isPackage
isCOMClass
hasMember
hasCopyConstructor
hasMoveConstructor
hasPostblit
identifier
fullyQualifiedName
getAliasThis
getAttributes
getBitfieldOffset
getBitfieldWidth
getFunctionAttributes
getFunctionVariadicStyle
getLinkage
getLocation
getMember
getOverloads
getParameterStorageClasses
getPointerBitmap
getCppNamespaces
getVisibility
getProtection
getTargetInfo
getVirtualFunctions
getVirtualMethods
getUnitTests
parent
child
classInstanceSize
classInstanceAlignment
getVirtualIndex
allMembers
derivedMembers
isSame
compiles
toType
initSymbol
parameters
fullyQualifiedName

TraitsArguments:
TraitsArgument
TraitsArgument , TraitsArguments

TraitsArgument:
AssignExpression
Type

Type Traits

isArithmetic

If the arguments are all either types that are arithmetic types, or expressions that are typed as arithmetic types, then true is returned. Otherwise, false is returned. If there are no arguments, false is returned.

Arithmetic types are integral types and floating point types.

import std.stdio;

void main()
{
int i;
writeln(__traits(isArithmetic, int));
writeln(__traits(isArithmetic, i, i+1, int));
writeln(__traits(isArithmetic));
writeln(__traits(isArithmetic, int*));
}

Prints:

true
true
false
false

isFloating

If the arguments are all either types that are floating point types, or expressions that are typed as floating point types, then true is returned. Otherwise, false is returned. If there are no arguments, false is returned.

The floating point types are: float, double, real, ifloat, idouble, ireal, cfloat, cdouble, creal, vectors of floating point types, and enums with a floating point base type.

import core.simd : float4;

enum E : float { a, b }

static assert(__traits(isFloating, float));
static assert(__traits(isFloating, E));
static assert(__traits(isFloating, float4));

static assert(!__traits(isFloating, float[4]));

isIntegral

If the arguments are all either types that are integral types, or expressions that are typed as integral types, then true is returned. Otherwise, false is returned. If there are no arguments, false is returned.

The integral types are: byte, ubyte, short, ushort, int, uint, long, ulong, cent, ucent, bool, char, wchar, dchar, vectors of integral types, and enums with an integral base type.

import core.simd : int4;

enum E { a, b }

static assert(__traits(isIntegral, bool));
static assert(__traits(isIntegral, char));
static assert(__traits(isIntegral, int));
static assert(__traits(isIntegral, E));
static assert(__traits(isIntegral, int4));

static assert(!__traits(isIntegral, float));
static assert(!__traits(isIntegral, int[4]));
static assert(!__traits(isIntegral, void*));

isScalar

If the arguments are all either types that are scalar types, or expressions that are typed as scalar types, then true is returned. Otherwise, false is returned. If there are no arguments, false is returned.

Scalar types are integral types, floating point types, pointer types, vectors of scalar types, and enums with a scalar base type.

import core.simd : int4, void16;

enum E { a, b }

static assert(__traits(isScalar, bool));
static assert(__traits(isScalar, char));
static assert(__traits(isScalar, int));
static assert(__traits(isScalar, float));
static assert(__traits(isScalar, E));
static assert(__traits(isScalar, int4));
static assert(__traits(isScalar, void*)); // Includes pointers!

static assert(!__traits(isScalar, int[4]));
static assert(!__traits(isScalar, void16));
static assert(!__traits(isScalar, void));
static assert(!__traits(isScalar, typeof(null)));
static assert(!__traits(isScalar, Object));

isUnsigned

If the arguments are all either types that are unsigned types, or expressions that are typed as unsigned types, then true is returned. Otherwise, false is returned. If there are no arguments, false is returned.

The unsigned types are: ubyte, ushort, uint, ulong, ucent, bool, char, wchar, dchar, vectors of unsigned types, and enums with an unsigned base type.

import core.simd : uint4;

enum SignedEnum { a, b }
enum UnsignedEnum : uint { a, b }

static assert(__traits(isUnsigned, bool));
static assert(__traits(isUnsigned, char));
static assert(__traits(isUnsigned, uint));
static assert(__traits(isUnsigned, UnsignedEnum));
static assert(__traits(isUnsigned, uint4));

static assert(!__traits(isUnsigned, int));
static assert(!__traits(isUnsigned, float));
static assert(!__traits(isUnsigned, SignedEnum));
static assert(!__traits(isUnsigned, uint[4]));
static assert(!__traits(isUnsigned, void*));

isStaticArray

Works like isArithmetic, except it's for static array types.

import core.simd : int4;

enum E : int[4] { a = [1, 2, 3, 4] }

static array = [1, 2, 3]; // Not a static array: the type is inferred as int[] not int[3].

static assert(__traits(isStaticArray, void[0]));
static assert(__traits(isStaticArray, E));
static assert(!__traits(isStaticArray, int4));
static assert(!__traits(isStaticArray, array));

isAssociativeArray

Works like isArithmetic, except it's for associative array types.

isAbstractClass

If the arguments are all either types that are abstract classes, or expressions that are typed as abstract classes, then true is returned. Otherwise, false is returned. If there are no arguments, false is returned.

import std.stdio;

abstract class C { int foo(); }

void main()
{
C c;
writeln(__traits(isAbstractClass, C));
writeln(__traits(isAbstractClass, c, C));
writeln(__traits(isAbstractClass));
writeln(__traits(isAbstractClass, int*));
}

Prints:

true
true
false
false

isFinalClass

Works like isAbstractClass, except it's for final classes.

isCopyable

Takes one argument. If that argument is a copyable type then true is returned, otherwise false.

struct S
{
}
static assert( __traits(isCopyable, S));

struct T
{
@disable this(this); // disable copy construction
}
static assert(!__traits(isCopyable, T));

isPOD

Takes one argument, which must be a type. It returns true if the type is a POD type, otherwise false.

toType

Takes a single argument, which must evaluate to an expression of type string. The contents of the string must correspond to the mangled contents of a type that has been seen by the implementation.

Only D mangling is supported. Other manglings, such as C++ mangling, are not.

The value returned is a type.

template Type(T) { alias Type = T; }

Type!(__traits(toType, "i")) j = 3; // j is declared as type `int`

static assert(is(Type!(__traits(toType, (int*).mangleof)) == int*));

__traits(toType, "i") x = 4; // x is also declared as type `int`

Rationale: Provides the inverse operation of the .mangleof property.

isZeroInit

Takes one argument which must be a type. If the type's default initializer is all zero bits then true is returned, otherwise false.

struct S1 { int x; }
struct S2 { int x = -1; }

static assert(__traits(isZeroInit, S1));
static assert(!__traits(isZeroInit, S2));

void test()
{
int x = 3;
static assert(__traits(isZeroInit, typeof(x)));
}

// `isZeroInit` will always return true for a class C
// because `C.init` is null reference.

class C { int x = -1; }

static assert(__traits(isZeroInit, C));

// For initializing arrays of element type `void`.
static assert(__traits(isZeroInit, void));

hasCopyConstructor

The argument is a type. If it is a struct with a copy constructor, returns true. Otherwise, return false. A copy constructor is distinct from a move constructor or a postblit.

import std.stdio;

struct S { }

class C { }

struct P
{
this(ref P rhs) {} // copy constructor
}

struct B
{
this(this) {} // postblit
}

void main()
{
writeln(__traits(hasCopyConstructor, S)); // false
writeln(__traits(hasCopyConstructor, C)); // false
writeln(__traits(hasCopyConstructor, P)); // true
writeln(__traits(hasCopyConstructor, B)); // false, this is a postblit
}

hasMoveConstructor

The argument is a type. If it is a struct with a move constructor, returns true. Otherwise, return false. A move constructor is distinct from a copy constructor or a postblit.

import std.stdio;

struct S
{
this(S rhs) {} // move constructor
}

class C { }

struct P
{
this(ref P rhs) {} // copy constructor
}

struct B
{
this(this) {} // postblit
}

void main()
{
writeln(__traits(hasMoveConstructor, S)); // true
writeln(__traits(hasMoveConstructor, C)); // false
writeln(__traits(hasMoveConstructor, P)); // false
writeln(__traits(hasMoveConstructor, B)); // false, this is a postblit
}

hasPostblit

The argument is a type. If it is a struct with a postblit, returns true. Otherwise, return false. Note a postblit is distinct from a copy constructor.

import std.stdio;

struct S
{
}

class C
{
}

struct P
{
this(ref P rhs) {}
}

struct B
{
this(this) {}
}

void main()
{
writeln(__traits(hasPostblit, S)); // false
writeln(__traits(hasPostblit, C)); // false
writeln(__traits(hasPostblit, P)); // false, this is a copy ctor
writeln(__traits(hasPostblit, B)); // true
}

getAliasThis

Takes one argument, a type. If the type has alias this declarations, returns a ValueSeq of the names (as strings) of the members used in those declarations. Otherwise returns an empty sequence.

alias AliasSeq(T...) = T;

struct S1
{
string var;
alias var this;
}
static assert(__traits(getAliasThis, S1) == AliasSeq!("var"));
static assert(__traits(getAliasThis, int).length == 0);

pragma(msg, __traits(getAliasThis, S1));
pragma(msg, __traits(getAliasThis, int));

Prints:

tuple("var")
tuple()

getPointerBitmap

The argument is a type. The result is an array of size_t describing the memory used by an instance of the given type.

The first element of the array is the size of the type (for classes it is the classInstanceSize).

The following elements describe the locations of GC managed pointers within the memory occupied by an instance of the type. For type T, there are T.sizeof / size_t.sizeof possible pointers represented by the bits of the array values.

This array can be used by a precise GC to avoid false pointers.

void main()
{
static class C
{
// implicit virtual function table pointer not marked
// implicit monitor field not marked, usually managed manually
C next;
size_t sz;
void* p;
void function () fn; // not a GC managed pointer
}

static struct S
{
size_t val1;
void* p;
C c;
byte[] arr;          // { length, ptr }
void delegate () dg; // { context, func }
}

static assert (__traits(getPointerBitmap, C) == [6*size_t.sizeof, 0b010100]);
static assert (__traits(getPointerBitmap, S) == [7*size_t.sizeof, 0b0110110]);
}

getVirtualFunctions

The same as getVirtualMethods, except that final functions that do not override anything are included.

getVirtualMethods

The first argument is a class type or an expression of class type. The second argument is a string that matches the name of one of the functions of that class. The result is a symbol sequence of the virtual overloads of that function. It does not include final functions that do not override anything.

import std.stdio;

class D
{
this() { }
~this() { }
void foo() { }
int foo(int) { return 2; }
}

void main()
{
D d = new D();

foreach (t; __traits(getVirtualMethods, D, "foo"))
writeln(typeid(typeof(t)));

alias b = typeof(__traits(getVirtualMethods, D, "foo"));
foreach (t; b)
writeln(typeid(t));

auto i = __traits(getVirtualMethods, d, "foo")[1](1);
writeln(i);
}

Prints:

void()
int()
void()
int()
2

classInstanceSize

Takes a single argument, which must evaluate to either a class type or an expression of class type. The result is of type size_t, and the value is the number of bytes in the runtime instance of the class type. It is based on the static type of a class, not the polymorphic type.

classInstanceAlignment

Takes a single argument, which must evaluate to either a class type or an expression of class type. The result is of type size_t, and the value is the alignment of a runtime instance of the class type. It is based on the static type of a class, not the polymorphic type.

initSymbol

Takes a single argument, which must evaluate to a class, struct or union type. Returns a const(void)[] that holds the initial state of any instance of the supplied type. The slice is constructed for any type T as follows:

- ptr points to either the initializer symbol of T or null if T is a zero-initialized struct/union.

- length is equal to the size of an instance, i.e. T.sizeof for a struct/union and __traits(classInstanceSize, T) for a class.

This trait matches the behaviour of TypeInfo.initializer() but can also be used when TypeInfo is not available.

This trait is not available during CTFE because the actual address of the initializer symbol will be set by the linker and hence is not available at compile time.

import core.stdc.stdlib;

class C
{
int i = 4;
}

/// Initializes a malloc'ed instance of `C`
void main()
{
const void[] initSym = __traits(initSymbol, C);

void* ptr = malloc(initSym.length);
scope (exit) free(ptr);

// Note: allocated memory will only be written to through `c`, so cast is safe
ptr[0..initSym.length] = cast(void[]) initSym[];

C c = cast(C) ptr;
assert(c.i == 4);
}

Function Traits

isDisabled

Takes one argument and returns true if it's a function declaration marked with @disable.

struct Foo
{
@disable void foo();
void bar(){}
}

static assert(__traits(isDisabled, Foo.foo));
static assert(!__traits(isDisabled, Foo.bar));

For any other declaration, even if @disable is a syntactically valid attribute, false is returned because the annotation has no effect.

@disable struct Bar{}

static assert(!__traits(isDisabled, Bar));

getVirtualIndex

Takes a single argument which must evaluate to a function. The result is a ptrdiff_t containing the index of that function within the vtable of the parent type. If the function passed in is final and does not override a virtual function, -1 is returned instead.

isVirtualFunction

The same as isVirtualMethod, except that final functions that don't override anything return true.

isVirtualMethod

Takes one argument. If that argument is a virtual function, true is returned, otherwise false. Final functions that don't override anything return false.

import std.stdio;

struct S
{
void bar() { }
}

class C
{
void bar() { }
}

void main()
{
writeln(__traits(isVirtualMethod, C.bar));  // true
writeln(__traits(isVirtualMethod, S.bar));  // false
}

isAbstractFunction

Takes one argument. If that argument is an abstract function, true is returned, otherwise false.

import std.stdio;

struct S
{
void bar() { }
}

class C
{
void bar() { }
}

class AC
{
abstract void foo();
}

void main()
{
writeln(__traits(isAbstractFunction, C.bar));   // false
writeln(__traits(isAbstractFunction, S.bar));   // false
writeln(__traits(isAbstractFunction, AC.foo));  // true
}

isFinalFunction

Takes one argument. If that argument is a final function, true is returned, otherwise false.

import std.stdio;

struct S
{
void bar() { }
}

class C
{
void bar() { }
final void foo();
}

final class FC
{
void foo();
}

void main()
{
writeln(__traits(isFinalFunction, C.bar));  // false
writeln(__traits(isFinalFunction, S.bar));  // false
writeln(__traits(isFinalFunction, C.foo));  // true
writeln(__traits(isFinalFunction, FC.foo)); // true
}

isOverrideFunction

Takes one argument. If that argument is a function marked with override, true is returned, otherwise false.

import std.stdio;

class Base
{
void foo() { }
}

class Foo : Base
{
override void foo() { }
void bar() { }
}

void main()
{
writeln(__traits(isOverrideFunction, Base.foo)); // false
writeln(__traits(isOverrideFunction, Foo.foo));  // true
writeln(__traits(isOverrideFunction, Foo.bar));  // false
}

isStaticFunction

Takes one argument. If that argument is a static function, meaning it has no context pointer, true is returned, otherwise false.

struct A
{
int foo() { return 3; }
static int boo(int a) { return a; }
}

void main()
{
assert(__traits(isStaticFunction, A.boo));
assert(!__traits(isStaticFunction, A.foo));
assert(__traits(isStaticFunction, main));
}

isReturnOnStack

Takes one argument which must either be a function symbol, function literal, a delegate, or a function pointer. It returns a bool which is true if the return value of the function is returned on the stack via a pointer to it passed as a hidden extra parameter to the function.

struct S { int[20] a; }
int test1();
S test2();

static assert(__traits(isReturnOnStack, test1) == false);
static assert(__traits(isReturnOnStack, test2) == true);

Implementation Defined: This is determined by the function ABI calling convention in use, which is often complex.

Best Practices: This has applications in:

1. Returning values in registers is often faster, so this can be used as a check on a hot function to ensure it is using the fastest method.

2. When using inline assembly to correctly call a function.

3. Testing that the compiler does this correctly is normally hackish and awkward, this enables efficient, direct, and simple testing.

getFunctionVariadicStyle

Takes one argument which must either be a function symbol, or a type that is a function, delegate or a function pointer. It returns a string identifying the kind of variadic arguments that are supported.

getFunctionVariadicStyle

result

kind

access

example

"none"

not a variadic function

void foo();

"argptr"

D style variadic function

_argptr and _arguments

void bar(...)

"stdarg"

C style variadic function

core.stdc.stdarg

extern (C) void abc(int, ...)

"typesafe"

typesafe variadic function

array on stack

void def(int[] ...)

import core.stdc.stdarg;

void novar() {}
extern(C) void cstyle(int, ...) {}
extern(C++) void cppstyle(int, ...) {}
void dstyle(...) {}
void typesafe(int[]...) {}

static assert(__traits(getFunctionVariadicStyle, novar) == "none");
static assert(__traits(getFunctionVariadicStyle, cstyle) == "stdarg");
static assert(__traits(getFunctionVariadicStyle, cppstyle) == "stdarg");
static assert(__traits(getFunctionVariadicStyle, dstyle) == "argptr");
static assert(__traits(getFunctionVariadicStyle, typesafe) == "typesafe");

static assert(__traits(getFunctionVariadicStyle, (int[] a...) {}) == "typesafe");
static assert(__traits(getFunctionVariadicStyle, typeof(cstyle)) == "stdarg");

getFunctionAttributes

Takes one argument which must either be a function symbol, function literal, or a function pointer. It returns a string ValueSeq of all the attributes of that function excluding any user-defined attributes (UDAs can be retrieved with the getAttributes trait). If no attributes exist it will return an empty sequence.
Note: The order of the attributes in the returned sequence is implementation-defined and should not be relied upon.
A list of currently supported attributes are:

- pure, nothrow, @nogc, @property, @system, @trusted, @safe, ref and @live

Note: ref is a function attribute even though it applies to the return type.
Additionally the following attributes are only valid for non-static member functions:

- const, immutable, inout, shared

For example:

int sum(int x, int y) pure nothrow { return x + y; }

pragma(msg, __traits(getFunctionAttributes, sum));

struct S
{
void test() const @system { }
}

pragma(msg, __traits(getFunctionAttributes, S.test));

Prints:

tuple("pure", "nothrow", "@system")
tuple("const", "@system")

Note that some attributes can be inferred. For example:

pragma(msg, __traits(getFunctionAttributes, (int x) @trusted { return x * 2; }));

Prints:

tuple("pure", "nothrow", "@nogc", "@trusted")

Function Parameter Traits

isRef, isOut, isLazy

Takes one argument. If that argument is a declaration, true is returned if it is ref, out, or lazy, otherwise false.

void fooref(ref int x)
{
static assert(__traits(isRef, x));
static assert(!__traits(isOut, x));
static assert(!__traits(isLazy, x));
}

void fooout(out int x)
{
static assert(!__traits(isRef, x));
static assert(__traits(isOut, x));
static assert(!__traits(isLazy, x));
}

void foolazy(lazy int x)
{
static assert(!__traits(isRef, x));
static assert(!__traits(isOut, x));
static assert(__traits(isLazy, x));
}

getParameterStorageClasses

Takes two arguments. The first must either be a function symbol, a function call, or a type that is a function, delegate or a function pointer. The second is an integer identifying which parameter, where the first parameter is 0. It returns a ValueSeq of strings representing the storage classes of that parameter.

ref int foo(return ref const int* p, scope int* a, out int b, lazy int c);

static assert(__traits(getParameterStorageClasses, foo, 0)[0] == "return");
static assert(__traits(getParameterStorageClasses, foo, 0)[1] == "ref");

static assert(__traits(getParameterStorageClasses, foo, 1)[0] == "scope");
static assert(__traits(getParameterStorageClasses, foo, 2)[0] == "out");
static assert(__traits(getParameterStorageClasses, typeof(&foo), 3)[0] == "lazy");

int* p, a;
int b, c;

static assert(__traits(getParameterStorageClasses, foo(p, a, b, c), 1)[0] == "scope");
static assert(__traits(getParameterStorageClasses, foo(p, a, b, c), 2)[0] == "out");
static assert(__traits(getParameterStorageClasses, foo(p, a, b, c), 3)[0] == "lazy");

parameters

May only be used inside a function. Takes no arguments, and returns an lvalue sequence of the enclosing function's parameters.

alias AliasSeq(A...) = A;

void f(int n, char c)
{
alias PS = __traits(parameters);
PS[0]++; // increment n
static assert(is(typeof(PS) == AliasSeq!(int, char)));

// output parameter names
static foreach (i, p; PS)
{
pragma(msg, __traits(identifier, p));
}
}

int add(int x, int y)
{
return x + y;
}

int forwardToAdd(int x, int y)
{
return add(__traits(parameters));
// equivalent to;
//return add(x, y);
}

If the function is nested, the parameters returned are those of the inner function, not the outer one.

int nestedExample(int x)
{
// outer function's parameters
static assert(typeof(__traits(parameters)).length == 1);

int add(int x, int y)
{
// inner function's parameters
static assert(typeof(__traits(parameters)).length == 2);
return x + y;
}

return add(x, x);
}

class C
{
int opApply(int delegate(size_t, C) dg)
{
if (dg(0, this)) return 1;
return 0;
}
}

void foreachExample(C c, int x)
{
foreach(idx; 0..5)
{
static assert(is(typeof(__traits(parameters)) == AliasSeq!(C, int)));
}
foreach(idx, elem; c)
{
//  __traits(parameters) sees past the delegate passed to opApply
static assert(is(typeof(__traits(parameters)) == AliasSeq!(C, int)));
}
}

Symbol Traits

fullyQualifiedName

Gets the fully qualified name of a type or symbol.

module myModule;

int i;
static assert(__traits(fullyQualifiedName, i) == "myModule.i");

struct MyStruct {}
static assert(__traits(fullyQualifiedName, const MyStruct[]) == "const(myModule.MyStruct[])");

isNested

Takes one argument. It returns true if the argument is a nested type which internally stores a context pointer, otherwise it returns false. Nested types can be classes, structs, and functions.

isFuture

Takes one argument. It returns true if the argument is a symbol marked with the @__future attribute, otherwise false. Currently, only functions and variable declarations have support for the @__future keyword.

isDeprecated

Takes one argument. It returns true if the argument is a symbol marked with the deprecated keyword, otherwise false.

deprecated("No longer supported")
int i;

struct A
{
int foo() { return 1; }

deprecated("please use foo")
int bar() { return 1; }
}

static assert(__traits(isDeprecated, i));
static assert(!__traits(isDeprecated, A.foo));
static assert(__traits(isDeprecated, A.bar));

isTemplate

Takes one argument. If that argument or any of its overloads is a template then true is returned, otherwise false.

void foo(T)(){}
static assert(__traits(isTemplate, foo));
static assert(!__traits(isTemplate, foo!int()));
static assert(!__traits(isTemplate, "string"));

isModule

Takes one argument. If that argument is a symbol that refers to a module then true is returned, otherwise false. Package modules are considered to be modules even if they have not been directly imported as modules.

import core.thread;
import std.algorithm.sorting;

// A regular package (no package.d)
static assert(!__traits(isModule, core));
// A package module (has a package.d file)
// Note that we haven't imported std.algorithm directly.
// (In other words, we don't have an "import std.algorithm;" directive.)
static assert(__traits(isModule, std.algorithm));
// A regular module
static assert(__traits(isModule, std.algorithm.sorting));

isPackage

Takes one argument. If that argument is a symbol that refers to a package then true is returned, otherwise false.

import std.algorithm.sorting;
static assert(__traits(isPackage, std));
static assert(__traits(isPackage, std.algorithm));
static assert(!__traits(isPackage, std.algorithm.sorting));

isCOMClass

Takes one argument. If that argument is a symbol that refers to a class declaration and is a COM class then true is retuned, otherwise false.

hasMember

The first argument is a type that has members, or is an expression of a type that has members. The second argument is a string. If the string is a valid property of the type, true is returned, otherwise false.

import std.stdio;

struct S
{
int m;
}

void main()
{
S s;

static assert(__traits(hasMember, S, "m"));
static assert(__traits(hasMember, s, "m"));
static assert(!__traits(hasMember, S, "y"));
static assert(!__traits(hasMember, S, "write")); // false, but callable like a member via UFCS
static assert(__traits(hasMember, int, "sizeof"));
static assert(__traits(hasMember, 5, "sizeof"));
}

identifier

Takes one argument, a symbol. Returns the identifier for that symbol as a string literal.

int var = 123;
static assert(__traits(identifier, var) == "var");

getAttributes

Takes one argument, a symbol. Returns a sequence of all attached user-defined attributes. If no UDAs exist it will return an empty sequence

For more information, see: User-Defined Attributes

@(3) int a;
@("string", 7) int b;

enum Foo;
@Foo int c;

pragma(msg, __traits(getAttributes, a));
pragma(msg, __traits(getAttributes, b));
pragma(msg, __traits(getAttributes, c));

Prints:

tuple(3)
tuple("string", 7)
tuple((Foo))

getBitfieldOffset

Takes one argument, a qualified name that resolve to a field in a struct or class.

If the field is a bitfield, it returns as a uint the bit number of the least significant bit in the field. The rightmost bit is at offset 0, the leftmost bit is at offset 31 (for 32 bit int fields).

If the field is a not bitfield, it returns as a uint the number 0.

struct S
{
int a,b;
int :2, c:3;
}

static assert(__traits(getBitfieldOffset, S.b) == 0);
static assert(__traits(getBitfieldOffset, S.c) == 2);

getBitfieldWidth

Takes one argument, a qualified name that resolve to a field in a struct or class.

If the field is a bitfield, it returns as a uint the width of the bit field as a number of bits.

If the field is a not bitfield, it returns the number of bits in the type.

struct S
{
int a,b;
int :2, c:3;
}

static assert(__traits(getBitfieldWidth, S.b) == 32);
static assert(__traits(getBitfieldWidth, S.c) == 3);

getLinkage

Takes one argument, which is a declaration symbol, or the type of a function, delegate, pointer to function, struct, class, or interface. Returns a string representing the LinkageAttribute of the declaration. The string is one of:

- "D"

- "C"

- "C++"

- "Windows"

- "Objective-C"

- "System"

extern (C) int fooc();
alias aliasc = fooc;

static assert(__traits(getLinkage, fooc) == "C");
static assert(__traits(getLinkage, aliasc) == "C");

extern (C++) struct FooCPPStruct {}
extern (C++) class FooCPPClass {}
extern (C++) interface FooCPPInterface {}

static assert(__traits(getLinkage, FooCPPStruct) == "C++");
static assert(__traits(getLinkage, FooCPPClass) == "C++");
static assert(__traits(getLinkage, FooCPPInterface) == "C++");

getLocation

Takes one argument which is a symbol. To disambiguate between overloads, pass the result of getOverloads with the desired index, to getLocation. Returns a ValueSeq of a string and two ints which correspond to the filename, line number and column number where the argument was declared.

getMember

Takes two arguments, the second must be a string. The result is an expression formed from the first argument, followed by a ‘.’, followed by the second argument as an identifier.

import std.stdio;

struct S
{
int mx;
static int my;
}

void main()
{
S s;

__traits(getMember, s, "mx") = 1;  // same as s.mx=1;
writeln(__traits(getMember, s, "m" ~ "x")); // 1

// __traits(getMember, S, "mx") = 1;  // error, no this for S.mx
__traits(getMember, S, "my") = 2;  // ok
}

getOverloads

- The first argument is an aggregate type or instance, or a module.

- The second argument is a string that matches the name of the member(s) to return.

- The third argument is a bool, and is optional. If true, the result will also include template overloads.

- The result is a symbol sequence of all the overloads of the supplied name.

import std.stdio;

class D
{
void foo() { }
int foo(int) { return 2; }
void bar(T)() { return T.init; }
class bar(int n) {}
}

void main()
{
D d = new D();

alias fooOverloads = __traits(getOverloads, D, "foo");
foreach (o; fooOverloads)
writeln(typeid(typeof(o)));

// typeof on a symbol sequence gives a type sequence
foreach (T; typeof(fooOverloads))
writeln(typeid(T));

// calls d.foo(3)
auto i = __traits(getOverloads, d, "foo")[1](3);
assert(i == 2);

// pass true to include templates
// calls std.stdio.writeln(i)
__traits(getOverloads, std.stdio, "writeln", true)[0](i);

foreach (o; __traits(getOverloads, D, "bar", true))
writeln(o.stringof);
}

Prints:

void function()
int function(int)
void function()
int function(int)
2
bar(T)()
bar(int n)

getCppNamespaces

The argument is a symbol. The result is a ValueSeq of strings, possibly empty, that correspond to the namespaces the symbol resides in.

extern(C++, "ns")
struct Foo {}
struct Bar {}
extern(C++, __traits(getCppNamespaces, Foo)) struct Baz {}
static assert(__traits(getCppNamespaces, Foo) ==  __traits(getCppNamespaces, Baz));
void main()
{
static assert(__traits(getCppNamespaces, Foo)[0] == "ns");
static assert(!__traits(getCppNamespaces, Bar).length);
static assert(__traits(getCppNamespaces, Foo) ==  __traits(getCppNamespaces, Baz));
}

getVisibility

The argument is a symbol. The result is a string giving its visibility level: "public", "private", "protected", "export", or "package".

import std.stdio;

class D
{
export void foo() { }
public int bar;
}

void main()
{
D d = new D();

auto i = __traits(getVisibility, d.foo);
writeln(i);

auto j = __traits(getVisibility, d.bar);
writeln(j);
}

Prints:

export
public

fullyQualifiedName

Takes one argument, which can be a type, expression, or symbol, and returns a string.

- A type returns a string representing the type.

- A expression returns a string representing the type of the expression.

- A symbol returns a string representing the fully qualified name of the symbol.

module plugh;
import std.stdio;

void main()
{
auto s = __traits(fullyQualifiedName, int);
writeln(s);

auto t = __traits(fullyQualifiedName, 1.0);
writeln(t);

auto u = __traits(fullyQualifiedName, t);
writeln(u);
}

Prints:

int
double
plugh.main.t

getProtection

A backward-compatible alias for getVisibility.

getTargetInfo

Receives a string key as argument. The result is an expression describing the requested target information.

version (CppRuntime_Microsoft)
static assert(__traits(getTargetInfo, "cppRuntimeLibrary") == "libcmt");

Keys are implementation defined, allowing relevant data for exotic targets. A reliable subset exists which are always available:

- "cppRuntimeLibrary" - The C++ runtime library affinity for this toolchain

- "cppStd" - The version of the C++ standard supported by extern(C++) code, equivalent to the __cplusplus macro in a C++ compiler

- "floatAbi" - Floating point ABI; may be "hard", "soft", or "softfp"

- "objectFormat" - Target object format

getUnitTests

Takes one argument, a symbol of an aggregate (e.g. struct/class/module). The result is a symbol sequence of all the unit test functions of that aggregate. The functions returned are like normal nested static functions, CTFE will work and UDAs will be accessible.

Note: The -unittest flag needs to be passed to the compiler. If the flag is not passed, __traits(getUnitTests) will always return an empty sequence.

module foo;

import core.runtime;
import std.stdio;

struct name { string name; }

class Foo
{
unittest
{
writeln("foo.Foo.unittest");
}
}

@name("foo") unittest
{
writeln("foo.unittest");
}

template Tuple (T...)
{
alias Tuple = T;
}

shared static this()
{
// Override the default unit test runner to do nothing. After that, "main" will
// be called.
Runtime.moduleUnitTester = { return true; };
}

void main()
{
writeln("start main");

alias tests = Tuple!(__traits(getUnitTests, foo));
static assert(tests.length == 1);

alias attributes = Tuple!(__traits(getAttributes, tests[0]));
static assert(attributes.length == 1);

foreach (test; tests)
test();

foreach (test; __traits(getUnitTests, Foo))
test();
}

By default, the above will print:

start main
foo.unittest
foo.Foo.unittest

parent

Takes a single argument which must evaluate to a symbol. The result is the symbol that is the parent of it.

child

Takes two arguments. The first must be a symbol or expression. The second is a symbol, such as an alias to a member of the first argument. The result is the second argument interpreted with its this context set to the value of the first argument.

import std.stdio;

struct A
{
int i;
int foo(int j) {
return i * j;
}
T bar(T)(T t) {
return i + t;
}
}

alias Ai = A.i;
alias Abar = A.bar!int;

void main()
{
A a;

__traits(child, a, Ai) = 3;
writeln(a.i);
writeln(__traits(child, a, A.foo)(2));
writeln(__traits(child, a, Abar)(5));
}

Prints:

3
6
8

allMembers

Takes a single argument, which must evaluate to either a module, a struct, a union, a class, an interface, an enum, or a template instantiation.

A sequence of string literals is returned, each of which is the name of a member of that argument combined with all of the members of its base classes (if the argument is a class). No name is repeated. Builtin properties are not included.

import std.stdio;

class D
{
this() { }
~this() { }
void foo() { }
int foo(int) { return 0; }
}

void main()
{
auto b = [ __traits(allMembers, D) ];
writeln(b);
// ["__ctor", "__dtor", "foo", "toString", "toHash", "opCmp", "opEquals",
// "Monitor", "factory"]
}

The order in which the strings appear in the result is not defined.

derivedMembers

Takes a single argument, which must evaluate to either a type or an expression of type. A sequence of string literals is returned, each of which is the name of a member of that type. No name is repeated. Base class member names are not included. Builtin properties are not included.

import std.stdio;

class D
{
this() { }
~this() { }
void foo() { }
int foo(int) { return 0; }
}

void main()
{
auto a = [__traits(derivedMembers, D)];
writeln(a);    // ["__ctor", "__dtor", "foo"]
}

The order in which the strings appear in the result is not defined.

isSame

Compares two arguments and evaluates to bool.

The result is true if the two arguments are the same symbol (once aliases are resolved).

struct S { }

int foo();
int bar();

static assert(__traits(isSame, foo, foo));
static assert(!__traits(isSame, foo, bar));
static assert(!__traits(isSame, foo, S));
static assert(__traits(isSame, S, S));
static assert(!__traits(isSame, object, S));
static assert(__traits(isSame, object, object));

alias daz = foo;
static assert(__traits(isSame, foo, daz));

isSame matches against non-instantiated templates.

struct Foo(T){
T x;
}

struct Bar(T){
T x;
}

struct Point(T){
T x;
T y;
}

enum isFooOrBar(alias FB) = __traits(isSame, FB, Foo) || __traits(isSame, FB, Bar);

static assert(isFooOrBar!(Foo));
static assert(isFooOrBar!(Bar));
static assert(!isFooOrBar!(Point));

The result is true if the two arguments are expressions made up of literals or enums that evaluate to the same value.

enum e = 3;
static assert(__traits(isSame, (e), 3));
static assert(__traits(isSame, 5, 2 + e));

If the two arguments are both lambda functions (or aliases to lambda functions), then they are compared for equality. For the comparison to be computed correctly, the following conditions must be met for both lambda functions:

1. The lambda function arguments must not have a template instantiation as an explicit argument type. Any other argument types (basic, user-defined, template) are supported.

2. The lambda function body must contain a single expression (no return statement) which contains only numeric values, manifest constants, enum values, function arguments and function calls. If the expression contains local variables or return statements, the function is considered incomparable.

If these constraints aren't fulfilled, the function is considered incomparable and the result is false.

static assert(__traits(isSame, (a, b) => a + b, (c, d) => c + d));
static assert(__traits(isSame, a => ++a, b => ++b));
static assert(!__traits(isSame, (int a, int b) => a + b, (a, b) => a + b));
static assert(__traits(isSame, (a, b) => a + b + 10, (c, d) => c + d + 10));

int f() { return 2; }

void test(alias pred)()
{
// f() from main is a different function from top-level f()
static assert(!__traits(isSame, (int a) => a + f(), pred));
}

void main()
{
// lambdas accessing local variables are considered incomparable
int b;
static assert(!__traits(isSame, a => a + b, a => a + b));

// lambdas calling other functions are comparable
int f() { return 3;}
static assert(__traits(isSame, a => a + f(), a => a + f()));
test!((int a) => a + f())();
}

class A
{
int a;
this(int a)
{
this.a = a;
}
}

class B
{
int a;
this(int a)
{
this.a = a;
}
}

static assert(__traits(isSame, (A a) => ++a.a, (A b) => ++b.a));
// lambdas with different data types are considered incomparable,
// even if the memory layout is the same
static assert(!__traits(isSame, (A a) => ++a.a, (B a) => ++a.a));

If the two arguments are tuples then the result is true if the two tuples, after expansion, have the same length and if each pair of nth argument respects the constraints previously specified.

import std.meta;

struct S { }

// like __traits(isSame,0,0) && __traits(isSame,1,1)
static assert(__traits(isSame, AliasSeq!(0,1), AliasSeq!(0,1)));
// like __traits(isSame,S,std.meta) && __traits(isSame,1,1)
static assert(!__traits(isSame, AliasSeq!(S,1), AliasSeq!(std.meta,1)));
// the length of the sequences is different
static assert(!__traits(isSame, AliasSeq!(1), AliasSeq!(1,2)));

compiles

Returns a bool true if all of the arguments compile (are semantically correct). The arguments can be symbols, types, or expressions that are syntactically correct. The arguments cannot be statements or declarations - instead these can be wrapped in a function literal expression.

If there are no arguments, the result is false.

static assert(!__traits(compiles));
static assert(__traits(compiles, 1 + 1)); // expression
static assert(__traits(compiles, typeof(1))); // type
static assert(__traits(compiles, object)); // symbol
static assert(__traits(compiles, 1, 2, 3, int, long));
static assert(!__traits(compiles, 3[1])); // semantic error
static assert(!__traits(compiles, 1, 2, 3, int, long, 3[1]));

enum n = 3;
// wrap a declaration/statement in a function literal
static assert(__traits(compiles, { int[n] arr; }));
static assert(!__traits(compiles, { foreach (e; n) {} }));

struct S
{
static int s1;
int s2;
}

static assert(__traits(compiles, S.s1 = 0));
static assert(!__traits(compiles, S.s2 = 0));
static assert(!__traits(compiles, S.s3));

int foo();

static assert(__traits(compiles, foo));
static assert(__traits(compiles, foo + 1)); // call foo with optional parens
static assert(!__traits(compiles, &foo + 1));

This is useful for:

- Giving better error messages (using static assert) inside generic code than the sometimes hard to follow compiler ones.

- Doing a finer grained specialization than template partial specialization allows for.

Error Handling

I came, I coded, I crashed. Julius C'ster

All programs have to deal with errors. Errors are unexpected conditions that are not part of the normal operation of a program. Examples of common errors are:

- Out of memory.

- Out of disk space.

- Invalid file name.

- Attempting to write to a read-only file.

- Attempting to read a non-existent file.

- Requesting a system service that is not supported.

The Error Handling Problem
The traditional C way of detecting and reporting errors is not traditional, it is ad-hoc and varies from function to function, including:

- Returning a NULL pointer.

- Returning a 0 value.

- Returning a non-zero error code.

- Requiring errno to be checked.

- Requiring that a function be called to check if the previous function failed.

To deal with these possible errors, tedious error handling code must be added to each function call. If an error happened, code must be written to recover from the error, and the error must be reported to the user in some user friendly fashion. If an error cannot be handled locally, it must be explicitly propagated back to its caller. The long list of errno values needs to be converted into appropriate text to be displayed. Adding all the code to do this can consume a large part of the time spent coding a project - and still, if a new errno value is added to the runtime system, the old code can not properly display a meaningful error message.

Good error handling code tends to clutter up what otherwise would be a neat and clean looking implementation.

Even worse, good error handling code is itself error prone, tends to be the least tested (and therefore buggy) part of the project, and is frequently simply omitted. The end result is likely a "blue screen of death" as the program failed to deal with some unanticipated error.

Quick and dirty programs are not worth writing tedious error handling code for, and so such utilities tend to be like using a table saw with no blade guards.
What's needed is an error handling philosophy and methodology such that:

- It is standardized - consistent usage makes it more useful.

- The result is reasonable even if the programmer fails to check for errors.

- Old code can be reused with new code without having to modify the old code to be compatible with new error types.

- No errors get inadvertently ignored.

- ‘Quick and dirty’ utilities can be written that still correctly handle errors.

- It is easy to make the error handling source code look good.

The D Error Handling Solution
Let's first make some observations and assumptions about errors:

- Errors are not part of the normal flow of a program. Errors are exceptional, unusual, and unexpected.

- Because errors are unusual, execution of error handling code is not performance critical.

- The normal flow of program logic is performance critical.

- All errors must be dealt with in some way, either by code explicitly written to handle them, or by some system default handling.

- The code that detects an error knows more about the error than the code that must recover from the error.

The solution is to use exception handling to report errors. All errors are objects derived from the abstract class Error. Error has a pure virtual function called toString() which produces a string with a human readable description of the error.

If code detects an error like "out of memory," then an Error is thrown with a message saying "Out of memory". The function call stack is unwound, looking for a handler for the Error. Finally blocks are executed as the stack is unwound. If an error handler is found, execution resumes there. If not, the default Error handler is run, which displays the message and terminates the program.

How does this meet our criteria?

It is standardized - consistent usage makes it more useful.

This is the D way, and is used consistently in the D runtime library and examples.

The result is reasonable result even if the programmer fails to check for errors.

If no catch handlers are there for the errors, then the program gracefully exits through the default error handler with an appropriate message.

Old code can be reused with new code without having to modify the old code to be compatible with new error types.

Old code can decide to catch all errors, or only specific ones, propagating the rest upwards. In any case, there is no more need to correlate error numbers with messages, the correct message is always supplied.

No errors get inadvertently ignored.

Error exceptions get handled one way or another. There is nothing like a NULL pointer return indicating an error, followed by trying to use that NULL pointer.

'Quick and dirty' utilities can be written that still correctly handle errors.

Quick and dirty code need not write any error handling code at all, and don't need to check for errors. The errors will be caught, an appropriate message displayed, and the program gracefully shut down all by default.

It is easy to make the error handling source code look good.

The try/catch/finally statements look a lot nicer than endless if (error) goto errorhandler; statements.

How does this meet our assumptions about errors?

Errors are not part of the normal flow of a program. Errors are exceptional, unusual, and unexpected.

D exception handling fits right in with that.

Because errors are unusual, execution of error handling code is not performance critical.

Exception handling stack unwinding is a relatively slow process.

The normal flow of program logic is performance critical.

Since the normal flow code does not have to check every function call for error returns, it can be realistically faster to use exception handling for the errors.

All errors must be dealt with in some way, either by code explicitly written to handle them, or by some system default handling.

If there's no handler for a particular error, it is handled by the runtime library default handler. If an error is ignored, it is because the programmer specifically added code to ignore an error, which presumably means it was intentional.

The code that detects an error knows more about the error than the code that must recover from the error.

There is no more need to translate error codes into human readable strings, the correct string is generated by the error detection code, not the error recovery code. This also leads to consistent error messages for the same error between applications.

Using exceptions to handle errors leads to another issue - how to write exception safe programs. Here's how.

Unit Tests

UnitTest:
unittest BlockStatement

Unit tests are a builtin framework of test cases applied to a module to determine if it is working properly. A D program can be run with unit tests enabled or disabled.

Unit tests are a special function defined like:

unittest
{
...test code...
}

Individual tests are specified in the unit test using AssertExpressions. Unlike AssertExpressions used elsewhere, the assert is not assumed to hold, and upon assert failure the program is still in a defined state.

There can be any number of unit test functions in a module, including within struct, union and class declarations. They are executed in lexical order.

Unit tests, when enabled, are run after all static initialization is complete and before the main() function is called.

For example, given a class Sum that is used to add two values, a unit test can be given:

class Sum
{
int add(int x, int y) { return x + y; }

unittest
{
Sum sum = new Sum;
assert(sum.add(3,4) == 7);
assert(sum.add(-2,0) == -2);
}
}

When unit tests are enabled, the version identifier unittest is predefined.

Attributed Unittests

A unittest may be attributed with any of the global function attributes. Such unittests are useful in verifying the given attribute(s) on a template function:

void myFunc(T)(T[] data)
{
if (data.length > 2)
data[0] = data[1];
}

@safe nothrow unittest
{
auto arr = [1,2,3];
myFunc(arr);
assert(arr == [2,2,3]);
}

This unittest verifies that myFunc contains only @safe, nothrow code. Although this can also be accomplished by attaching these attributes to myFunc itself, that would prevent myFunc from being instantiated with types T that have @system or throwing code in their opAssign method, or other methods that myFunc may call. The above idiom allows myFunc to be instantiated with such types, yet at the same time verify that the @system and throwing behavior is not introduced by the code within myFunc itself.

Implementation Defined:

1. If unit tests are not enabled, the implementation is not required to check the UnitTest for syntactic or semantic correctness. This is to reduce the compile time impact of larger unit test sections. The tokens must still be valid, and the implementation can merely count { and } tokens to find the end of the UnitTest's BlockStatement.

2. The presentation of unit test results to the user.

3. The method used to enable or disable the unit tests. Use of a compiler switch such as -unittest to enable them is suggested.

4. The order in which modules are called to run their unit tests.

5. Whether the program stops on the first unit test failure, or continues running the unit tests.

Best Practices:

1. Using unit tests in conjunction with coverage testing (such as -cov) is effective.

2. A unit test for a function should appear immediately following it.

Documented Unittests

Documented unittests allow the developer to deliver code examples to the user, while at the same time automatically verifying that the examples are valid. This avoids the frequent problem of having outdated documentation for some piece of code.

If a declaration is followed by a documented unittest, the code in the unittest will be inserted in the example section of the declaration:

/// Math class
class Math
{
/// add function
static int add(int x, int y) { return x + y; }

///
unittest
{
assert(add(2, 2) == 4);
}
}

///
unittest
{
auto math = new Math();
auto result = math.add(2, 2);
}

The above will generate the following documentation:

class Math;

Math class

Example:

auto math = new Math;
auto result = math.add(2, 2);

int add(int x, int y);

add function

Example:

assert(add(2, 2) == 4);

A unittest which is not documented, or is marked as private will not be used to generate code samples.

There can be multiple documented unittests and they can appear in any order. They will be attached to the last non-unittest declaration:

/// add function
int add(int x, int y) { return x + y; }

/// code sample generated
unittest
{
assert(add(1, 1) == 2);
}

/// code sample not generated because the unittest is private
private unittest
{
assert(add(2, 2) == 4);
}

unittest
{
/// code sample not generated because the unittest isn't documented
assert(add(3, 3) == 6);
}

/// code sample generated, even if it only includes comments (or is empty)
unittest
{
/** assert(add(4, 4) == 8); */
}

The above will generate the following documentation:

int add(int x, int y);

add function

Examples:
code sample generated

assert(add(1, 1) == 2);

Examples:
code sample generated, even if it is empty or only includes comments

/** assert(add(4, 4) == 8); */

Garbage Collection

D is a systems programming language with support for garbage collection. Usually it is not necessary to free memory explicitly. Just allocate as needed, and the garbage collector will periodically return all unused memory to the pool of available memory.

D also provides the mechanisms to write code where the garbage collector is not involved. More information is provided below.

Programmers accustomed to explicitly managing memory allocation and deallocation will likely be skeptical of the benefits and efficacy of garbage collection. Experience both with new projects written with garbage collection in mind, and converting existing projects to garbage collection shows that:

- Garbage collected programs are often faster. This is counterintuitive, but the reasons are:

- Reference counting is a common solution to solve explicit memory allocation problems. The code to implement the increment and decrement operations whenever assignments are made is one source of slowdown. Hiding it behind smart pointer classes doesn't help the speed. (Reference counting methods are not a general solution anyway, as circular references never get deleted.)

- Destructors are used to deallocate resources acquired by an object. For most classes, this resource is allocated memory. With garbage collection, most destructors then become empty and can be discarded entirely.

- All those destructors freeing memory can become significant when objects are allocated on the stack. For each one, some mechanism must be established so that if an exception happens, the destructors all get called in each frame to release any memory they hold. If the destructors become irrelevant, then there's no need to set up special stack frames to handle exceptions, and the code runs faster.

- Garbage collection kicks in only when memory gets tight. When memory is not tight, the program runs at full speed and does not spend any time tracing and freeing memory.

- Garbage collected programs do not suffer from gradual deterioration due to an accumulation of memory leaks.

- Garbage collectors reclaim unused memory, therefore they do not suffer from "memory leaks" which can cause long running applications to gradually consume more and more memory until they bring down the system. GC programs have longer term stability.

- Garbage collected programs have fewer hard-to-find pointer bugs. This is because there are no dangling references to freed memory. There is no code to explicitly manage memory, hence no bugs in such code.

- Garbage collected programs are faster to develop and debug, because there's no need for developing, debugging, testing, or maintaining the explicit deallocation code.

Garbage collection is not a panacea. There are some downsides:

- It is not always obvious when the GC allocates memory, which in turn can trigger a collection, so the program can pause unexpectedly.

- The time it takes for a collection to complete is not bounded. While in practice it is very quick, this cannot normally be guaranteed.

- Normally, all threads other than the collector thread must be halted while the collection is in progress.

- Garbage collectors can keep around some memory that an explicit deallocator would not.

- Garbage collection should be implemented as a basic operating system kernel service. But since it is not, garbage collecting programs must carry around with them the garbage collection implementation. While this can be a shared library, it is still there.

These constraints are addressed by techniques outlined in Memory Management, including the mechanisms provided by D to control allocations outside the GC heap.

There is currently work in progress to make the runtime library free of GC heap allocations, to allow its use in scenarios where the use of GC infrastructure is not possible.

How Garbage Collection Works

The GC works by:

1. Stopping all other threads than the thread currently trying to allocate GC memory.

2. ‘Hijacking’ the current thread for GC work.

3. Scanning all ‘root’ memory ranges for pointers into GC allocated memory.

4. Recursively scanning all allocated memory pointed to by roots looking for more pointers into GC allocated memory.

5. Freeing all GC allocated memory that has no active pointers to it and do not need destructors to run.

6. Queueing all unreachable memory that needs destructors to run.

7. Resuming all other threads.

8. Running destructors for all queued memory.

9. Freeing any remaining unreachable memory.

10. Returning the current thread to whatever work it was doing.

Interfacing Garbage Collected Objects With Foreign Code

The garbage collector looks for roots in:

1. the static data segment

2. the stacks and register contents of each thread

3. the TLS (thread-local storage) areas of each thread

4. any roots added by core.memory.GC.addRoot() or core.memory.GC.addRange()

If the only pointer to an object is held outside of these areas, then the collector will miss it and free the memory.

To avoid this from happening, either

- maintain a pointer to the object in an area the collector does scan for pointers;

- add a root where a pointer to the object is stored using core.memory.GC.addRoot() or core.memory.GC.addRange().

- reallocate and copy the object using the foreign code's storage allocator or using the C runtime library's malloc/free.

Pointers and the Garbage Collector

Pointers in D can be broadly divided into two categories: Those that point to garbage collected memory, and those that do not. Examples of the latter are pointers created by calls to C's malloc(), pointers received from C library routines, pointers to static data, pointers to objects on the stack, etc. For those pointers, anything that is legal in C can be done with them.

For garbage collected pointers and references, however, there are some restrictions. These restrictions are minor, but they are intended to enable the maximum flexibility in garbage collector design.

Undefined Behavior:

- Do not xor pointers with other values, like the xor pointer linked list trick used in C.

- Do not use the xor trick to swap two pointer values.

- Do not store pointers into non-pointer variables using casts and other tricks.

void* p;
...
int x = cast(int)p;   // error: undefined behavior

The garbage collector does not scan non-pointer fields for GC pointers.

- Do not take advantage of alignment of pointers to store bit flags in the low order bits:

p = cast(void*)(cast(int)p | 1);  // error: undefined behavior

- Do not store into pointers values that may point into the garbage collected heap:

p = cast(void*)12345678;   // error: undefined behavior

A copying garbage collector may change this value.

- Do not store magic values into pointers, other than null.

- Do not write pointer values out to disk and read them back in again.

- Do not use pointer values to compute a hash function. A copying garbage collector can arbitrarily move objects around in memory, thus invalidating the computed hash value.

- Do not depend on the ordering of pointers:

if (p1 < p2)  // error: undefined behavior
...

since, again, the garbage collector can move objects around in memory.

- Do not add or subtract an offset to a pointer such that the result points outside of the bounds of the garbage collected object originally allocated.

char* p = new char[10];
char* q = p + 6; // ok
q = p + 11;      // error: undefined behavior
q = p - 1;       // error: undefined behavior

- Do not misalign pointers if those pointers may point into the GC heap, such as:

struct Foo
{
align (1):
byte b;
char* p;  // misaligned pointer
}

Misaligned pointers may be used if the underlying hardware supports them and the pointer is never used to point into the GC heap.

- Do not use byte-by-byte memory copies to copy pointer values. This may result in intermediate conditions where there is not a valid pointer, and if the GC pauses the thread in such a condition, it can corrupt memory. Most implementations of memcpy() will work since the internal implementation of it does the copy in aligned chunks greater than or equal to the pointer size, but since this kind of implementation is not guaranteed by the C standard, use memcpy() only with extreme caution.

- Do not have pointers in a struct instance that point back to the same instance. The trouble with this is if the instance gets moved in memory, the pointer will point back to where it came from, with likely disastrous results.

Things that are reliable and can be done:

- Use a union to share storage with a pointer:

union U { void* ptr; int value }

- A pointer to the start of a garbage collected object need not be maintained if a pointer to the interior of the object exists.

char[] p = new char[10];
char[] q = p[3..6];
// q is enough to hold on to the object, don't need to keep
// p as well.

One can avoid using pointers anyway for most tasks. D provides features rendering most explicit pointer uses obsolete, such as reference objects, dynamic arrays, and garbage collection. Pointers are provided in order to interface successfully with C APIs and for some low level work.

Working with the Garbage Collector

Garbage collection doesn't solve every memory deallocation problem. For example, if a pointer to a large data structure is kept, the garbage collector cannot reclaim it, even if it is never referred to again. To eliminate this problem, it is good practice to set a reference or pointer to an object to null when no longer needed.

This advice applies only to static references or references embedded inside other objects. There is not much point for such stored on the stack to be nulled because new stack frames are initialized anyway.

Object Pinning and a Moving Garbage Collector

Although D does not currently use a moving garbage collector, by following the rules listed above one can be implemented. No special action is required to pin objects. A moving collector will only move objects for which there are no ambiguous references, and for which it can update those references. All other objects will be automatically pinned.

D Operations That Involve the Garbage Collector

Some sections of code may need to avoid using the garbage collector. The following constructs may allocate memory using the garbage collector:

- NewExpression

- Array appending

- Array concatenation

- Array literals (see exceptions)

- Associative array literals

- Any insertion or removal in an associative array

- Extracting keys or values from an associative array

- Taking the address of (i.e. making a delegate to) a nested function that accesses variables in an outer scope

- A function literal that accesses variables in an outer scope

- An AssertExpression that fails its condition

There is a @nogc function attribute which will error at compile-time if any construct is used which would make a GC allocation.

Configuring the Garbage Collector

Since version 2.067, The garbage collector can now be configured through the command line, the environment or by options embedded into the executable.

By default, GC options can only be passed on the command line of the program to run, e.g.

app "--DRT-gcopt=profile:1 minPoolSize:16" arguments to app

Available GC options are:

- disable:0|1 - start disabled

- profile:0|1 - enable profiling with summary when terminating program

- gc:conservative|precise|manual - select GC implementation (default = conservative)

- initReserve:N - initial memory to reserve in MB

- minPoolSize:N - initial and minimum pool size in MB

- maxPoolSize:N - maximum pool size in MB

- incPoolSize:N - pool size increment MB

- parallel:N - number of additional threads for marking

- heapSizeFactor:N - targeted heap size to used memory ratio

- cleanup:none|collect|finalize - how to treat live objects when terminating

- collect: run a collection (the default for backward compatibility)

- none: do nothing

- finalize: all live objects are finalized unconditionally

In addition, --DRT-gcopt=help will show the list of options and their current settings.

Command line options starting with "--DRT-" are filtered out before calling main, so the program will not see them. They are still available via rt_args.

Configuration via the command line can be disabled by declaring a variable for the linker to pick up before using its default from the runtime:

extern(C) __gshared bool rt_cmdline_enabled = false;

Likewise, declare a boolean rt_envvars_enabled to enable configuration via the environment variable DRT_GCOPT:

extern(C) __gshared bool rt_envvars_enabled = true;

Setting default configuration properties in the executable can be done by specifying an array of options named rt_options:

extern(C) __gshared string[] rt_options = [ "gcopt=initReserve:100 profile:1" ];

Evaluation order of options is rt_options, then environment variables, then command line arguments, i.e. if command line arguments are not disabled, they can override options specified through the environment or embedded in the executable.

Precise Heap Scanning

Selecting precise as the garbage collector via the options above means type information will be used to identify actual or possible pointers or references within heap allocated data objects. Non-pointer data will not be interpreted as a reference to other memory as a "false pointer". The collector has to make pessimistic assumptions if a memory slot can contain both a pointer or an integer value, it will still be scanned (e.g. in a union).

To use the GC memory functions from core.memory for data with a mixture of pointers and non-pointer data, pass the TypeInfo of the allocated struct, class, or type as the optional parameter. The default null is interpreted as memory that might contain pointers everywhere.

struct S { size_t hash; Data* data; }
S* s = cast(S*)GC.malloc(S.sizeof, 0, typeid(S));

Attention: Enabling precise scanning needs slightly more caution with type declarations. For example, when reserving a buffer as part of a struct and later emplacing an object instance with references to other allocations into this memory, do not use basic integer types to reserve the space. Doing so will cause the garbage collector not to detect the references. Instead, use an array type that will scan this area conservatively. Using void* is usually the best option as it also ensures proper alignment for pointers being scanned by the GC.

Precise Scanning of the DATA and TLS segment

Windows only: As of version 2.075, the DATA (global shared data) and TLS segment (thread local data) of an executable or DLL can be configured to be scanned precisely by the garbage collector instead of conservatively. This takes advantage of information emitted by the compiler to identify possible mutable pointers inside these segments. Immutable pointers with initializers are excluded from scanning, too, as they can only point to preallocated memory.

Precise scanning can be enabled with the D runtime option "scanDataSeg". Possible option values are "conservative" (default) and "precise". As with the GC options, it can be specified on the command line, in the environment or embedded into the executable, e.g.

extern(C) __gshared string[] rt_options = [ "scanDataSeg=precise" ];

Attention: Enabling precise scanning needs slightly more caution typing global memory. For example, to pre-allocate memory in the DATA/TLS segment and later emplace an object instance with references to other allocations into this memory, do not use basic integer types to reserve the space. Doing so will cause the garbage collector not to detect the references. Instead, use an array type that will scan this area conservatively. Using void* is usually the best option as it also ensures proper alignment for pointers being scanned by the GC.

class Singleton { void[] mem; }
align(__traits(classInstanceAlignment, Singleton))
void*[(__traits(classInstanceSize, Singleton) - 1) / (void*).sizeof + 1]
singleton_store;
static this()
{
emplace!Singleton(singleton_store).mem = allocateMem();
}
Singleton singleton() { return cast(Singleton)singleton_store.ptr; }

For precise typing of that area, let the compiler generate the class instance into the DATA segment:

class Singleton { void[] mem; }
shared(Singleton) singleton = new Singleton;
shared static this() { singleton.mem = allocateSharedMem(); }

This doesn't work for TLS memory, though. See also: void arrays.
Parallel marking

By default the garbage collector uses all available CPU cores to mark the heap.

This might affect your application if it has threads that are not suspended during the mark phase of the collection. Configure the number of additional threads used for marking by GC option parallel, e.g. by passing --DRT-gcopt=parallel:2 on the command line or embedding the option into the binary via rt_options. The number of threads actually created is limited to core.cpuid.threadsPerCPU-1. A value of 0 disables parallel marking completely.

Adding your own Garbage Collector

GC implementations are added to a registry that allows to supply more implementations by just linking them into the binary. To do so add a function that is executed before the D runtime initialization using pragma(crt_constructor):

import core.gc.gcinterface, core.gc.registry;
extern (C) pragma(crt_constructor) void registerMyGC()
{
registerGCFactory("mygc", &createMyGC);
}

GC createMyGC()
{
__gshared instance = new MyGC;
instance.initialize();
return instance;
}

class MyGC : GC { /*...*/ }

[The GC modules defining the interface (gc.interface) and registration (gc.registry) are currently not public and are subject to change from version to version. Add an import search path to the druntime/src path to compile the example.]

The new GC is added to the list of available garbage collectors that can be selected via the usual configuration options, e.g. by embedding rt_options into the binary:

extern (C) __gshared string[] rt_options = ["gcopt=gc:mygc"];

The standard GC implementation from a statically linked binary can be removed by redefining the function extern(C) void* register_default_gcs(). If no custom garbage collector has been registered all attempts to allocate GC managed memory will terminate the application with an appropriate message.

References

- Wikipedia

- GC FAQ

- Uniprocessor Garbage Collector Techniques

- Garbage Collection: Algorithms for Automatic Dynamic Memory Management

Floating-Point

Floating-Point Intermediate Values

For floating-point operations and expression intermediate values, a greater precision can be used than the type of the expression. Only the minimum precision is set by the types of the operands, not the maximum. Implementation Note: On Intel x86 machines, for example, it is expected (but not required) that the intermediate calculations be done to the full 80 bits of precision implemented by the hardware.

Execution of floating-point expressions may yield a result of greater precision than dictated by the source.

Floating-Point Constant Folding

Regardless of the type of the operands, floating-point constant folding is done in real or greater precision. It is always done following IEEE-754 rules and round-to-nearest is used.

Floating-point constants are internally represented in the implementation in at least real precision, regardless of the constant's type. The extra precision is available for constant folding. Committing to the precision of the result is done as late as possible in the compilation process. For example:

const float f = 0.2f;
writeln(f - 0.2);

will print 0. A non-const static variable's value cannot be propagated at compile time, so:

static float f = 0.2f;
writeln(f - 0.2);

will print 2.98023e-09. Hex floating-point constants can also be used when specific floating-point bit patterns are needed that are unaffected by rounding. To find the hex value of 0.2f:

import std.stdio;

void main()
{
writefln("%a", 0.2f);
}

which is 0x1.99999ap-3. Using the hex constant:

const float f = 0x1.99999ap-3f;
writeln(f - 0.2);

prints 2.98023e-09.

Different compiler settings, optimization settings, and inlining settings can affect opportunities for constant folding, therefore the results of floating-point calculations may differ depending on those settings.

Rounding Control

IEEE 754 floating-point arithmetic includes the ability to set 4 different rounding modes. These are accessible via the functions in core.stdc.fenv.

If the floating-point rounding mode is changed within a function, it must be restored before the function exits. If this rule is violated (for example, by the use of inline asm), the rounding mode used for subsequent calculations is undefined.

Exception Flags

IEEE 754 floating-point arithmetic can set several flags based on what happened with a computation:

FE_INVALID

FE_DENORMAL

FE_DIVBYZERO

FE_OVERFLOW

FE_UNDERFLOW

FE_INEXACT

These flags can be set/reset via the functions in core.stdc.fenv.

Floating-Point Transformations

An implementation may perform transformations on floating-point computations in order to reduce their strength.

Not all transformations are valid: The following transformations of floating-point expressions are not allowed because under IEEE rules they could produce different results.

Disallowed Floating-Point Transformations

transformation

comments

x + 0 → x

not valid if x is -0

x - 0 → x

not valid if x is ±0 and rounding is towards -∞

-x ↔ 0 - x

not valid if x is +0

x - x → 0

not valid if x is NaN or ±∞

x - y ↔ -(y - x)

not valid because (1-1=+0) whereas -(1-1)=-0

x * 0 → 0

not valid if x is NaN or ±∞

x / c ↔ x * (1/c)

valid if (1/c) yields an exact result

x != x → false

not valid if x is a NaN

x == x → true

not valid if x is a NaN

x !op y ↔ !(x op y)

not valid if x or y is a NaN

Of course, transformations that would alter side effects are also invalid.

D x86 Inline Assembler

D, being a systems programming language, provides an inline assembler. The inline assembler is standardized for D implementations across the same CPU family, for example, the Intel Pentium inline assembler for a Win32 D compiler will be syntax compatible with the inline assembler for Linux running on an Intel Pentium.

Implementations of D on different architectures, however, are free to innovate upon the memory model, function call/return conventions, argument passing conventions, etc.

This document describes the x86 and x86_64 implementations of the inline assembler. The inline assembler platform support that a compiler provides is indicated by the D_InlineAsm_X86 and D_InlineAsm_X86_64 version identifiers, respectively.

Asm statement

AsmStatement:
asm FunctionAttributesopt { AsmInstructionListopt }

AsmInstructionList:
AsmInstruction ;
AsmInstruction ; AsmInstructionList

Assembler instructions must be located inside an asm block. Like functions, asm statements must be anotated with adequate function attributes to be compatible with the caller. Asm statements attributes must be explicitly defined, they are not inferred.

@safe is not allowed as an attribute, as the compiler does no safety checking of assembly statements - use @trusted instead.

void ok() pure nothrow @safe @nogc
{
asm pure nothrow @trusted @nogc
{}
}

void error() @safe @nogc
{
asm @nogc // Error: asm statement is assumed to be @system - mark it with '@trusted' if it is not
{}
asm @safe @nogc // Deprecation: asm statement cannot be @safe, use @trusted instead
{}
}

Asm instruction

AsmInstruction:
Identifier : AsmInstruction
align IntegerExpression
even
naked
db Operands
ds Operands
di Operands
dl Operands
df Operands
dd Operands
de Operands
db StringLiteral
ds StringLiteral
di StringLiteral
dl StringLiteral
dw StringLiteral
dq StringLiteral
Opcode
Opcode Operands

Opcode:
Identifier
int
in
out

Operands:
Operand
Operand , Operands

Labels

Assembler instructions can be labeled just like other statements. They can be the target of goto statements. For example:

void *pc;
asm
{
call L1          ;
L1:                ;
pop  EBX         ;
mov  pc[EBP],EBX ; // pc now points to code at L1
}

align IntegerExpression

IntegerExpression:
IntegerLiteral
Identifier

Causes the assembler to emit NOP instructions to align the next assembler instruction on an IntegerExpression boundary. IntegerExpression must evaluate at compile time to an integer that is a power of 2.

Aligning the start of a loop body can sometimes have a dramatic effect on the execution speed.

even

Causes the assembler to emit NOP instructions to align the next assembler instruction on an even boundary.

naked

Causes the compiler to not generate the function prolog and epilog sequences. This means such is the responsibility of inline assembly programmer, and is normally used when the entire function is to be written in assembler.

db, ds, di, dl, df, dd, de

These pseudo ops are for inserting raw data directly into the code. db is for bytes, ds is for 16 bit words, di is for 32 bit words, dl is for 64 bit words, df is for 32 bit floats, dd is for 64 bit doubles, and de is for 80 bit extended reals. Each can have multiple operands. If an operand is a string literal, it is as if there were length operands, where length is the number of characters in the string. One character is used per operand. For example:

asm
{
db 5,6,0x83;   // insert bytes 0x05, 0x06, and 0x83 into code
ds 0x1234;     // insert bytes 0x34, 0x12
di 0x1234;     // insert bytes 0x34, 0x12, 0x00, 0x00
dl 0x1234;     // insert bytes 0x34, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
df 1.234;      // insert float 1.234
dd 1.234;      // insert double 1.234
de 1.234;      // insert real 1.234
db "abc";      // insert bytes 0x61, 0x62, and 0x63
ds "abc";      // insert bytes 0x61, 0x00, 0x62, 0x00, 0x63, 0x00
}

Opcodes

A list of supported opcodes is at the end.

The following registers are supported. Register names are always in upper case.

Register:
AL
AH
AX
EAX

BL
BH
BX
EBX

CL
CH
CX
ECX

DL
DH
DX
EDX

BP
EBP

SP
ESP

DI
EDI

SI
ESI

ES
CS
SS
DS
GS
FS

CR0
CR2
CR3
CR4

DR0
DR1
DR2
DR3
DR6
DR7

TR3
TR4
TR5
TR6
TR7

ST

ST(0)
ST(1)
ST(2)
ST(3)
ST(4)
ST(5)
ST(6)
ST(7)

MM0
MM1
MM2
MM3
MM4
MM5
MM6
MM7

XMM0
XMM1
XMM2
XMM3
XMM4
XMM5
XMM6
XMM7

x86_64 adds these additional registers.

Register64:
RAX
RBX
RCX
RDX

BPL
RBP

SPL
RSP

DIL
RDI

SIL
RSI

R8B
R8W
R8D
R8

R9B
R9W
R9D
R9

R10B
R10W
R10D
R10

R11B
R11W
R11D
R11

R12B
R12W
R12D
R12

R13B
R13W
R13D
R13

R14B
R14W
R14D
R14

R15B
R15W
R15D
R15

XMM8
XMM9
XMM10
XMM11
XMM12
XMM13
XMM14
XMM15

YMM0
YMM1
YMM2
YMM3
YMM4
YMM5
YMM6
YMM7

YMM8
YMM9
YMM10
YMM11
YMM12
YMM13
YMM14
YMM15

Special Cases

lock, rep, repe, repne, repnz, repz

These prefix instructions do not appear in the same statement as the instructions they prefix; they appear in their own statement. For example:

asm { rep ; movsb ; }
pause

This opcode is not supported by the assembler, instead use

asm
{
rep  ;
nop  ;
}

which produces the same result.

floating point ops

Use the two operand form of the instruction format;

fdiv ST(1);     // wrong
fmul ST;        // wrong
fdiv ST,ST(1);  // right
fmul ST,ST(0);  // right

Operands

Operand:
AsmExp

AsmExp:
AsmLogOrExp
AsmLogOrExp ? AsmExp : AsmExp

AsmLogOrExp:
AsmLogAndExp
AsmLogOrExp || AsmLogAndExp

AsmLogAndExp:
AsmOrExp
AsmLogAndExp && AsmOrExp

AsmOrExp:
AsmXorExp
AsmOrExp | AsmXorExp

AsmXorExp:
AsmAndExp
AsmXorExp ^ AsmAndExp

AsmAndExp:
AsmEqualExp
AsmAndExp & AsmEqualExp

AsmEqualExp:
AsmRelExp
AsmEqualExp == AsmRelExp
AsmEqualExp != AsmRelExp

AsmRelExp:
AsmShiftExp
AsmRelExp < AsmShiftExp
AsmRelExp <= AsmShiftExp
AsmRelExp > AsmShiftExp
AsmRelExp >= AsmShiftExp

AsmShiftExp:
AsmAddExp
AsmShiftExp << AsmAddExp
AsmShiftExp >> AsmAddExp
AsmShiftExp >>> AsmAddExp

AsmAddExp:
AsmMulExp
AsmAddExp + AsmMulExp
AsmAddExp - AsmMulExp

AsmMulExp:
AsmBrExp
AsmMulExp * AsmBrExp
AsmMulExp / AsmBrExp
AsmMulExp % AsmBrExp

AsmBrExp:
AsmUnaExp
AsmBrExp [ AsmExp ]

AsmUnaExp:
AsmTypePrefix AsmExp
offsetof AsmExp
seg AsmExp
+ AsmUnaExp
- AsmUnaExp
! AsmUnaExp
~ AsmUnaExp
AsmPrimaryExp

AsmPrimaryExp:
IntegerLiteral
FloatLiteral
__LOCAL_SIZE
$
Register
Register : AsmExp
Register64
Register64 : AsmExp
DotIdentifier
this

DotIdentifier:
Identifier
Identifier . DotIdentifier
FundamentalType . Identifier

The operand syntax more or less follows the Intel CPU documentation conventions. In particular, the convention is that for two operand instructions the source is the right operand and the destination is the left operand. The syntax differs from that of Intel's in order to be compatible with the D language tokenizer and to simplify parsing.

The seg means load the segment number that the symbol is in. This is not relevant for flat model code. Instead, do a move from the relevant segment register.

A dotted expression is evaluated during the compilation and then must either give a constant or indicate a higher level variable that fits in the target register or variable.

Operand Types

AsmTypePrefix:
near ptr
far ptr
word ptr
dword ptr
qword ptr
FundamentalType ptr

In cases where the operand size is ambiguous, as in:

add [EAX],3     ;

it can be disambiguated by using an AsmTypePrefix:

add  byte ptr [EAX],3 ;
add  int ptr [EAX],7  ;

far ptr is not relevant for flat model code.

Struct/Union/Class Member Offsets

To access members of an aggregate, given a pointer to the aggregate is in a register, use the .offsetof property of the qualified name of the member:

struct Foo { int a,b,c; }
int bar(Foo *f)
{
asm
{
mov EBX,f                   ;
mov EAX,Foo.b.offsetof[EBX] ;
}
}
void main()
{
Foo f = Foo(0, 2, 0);
assert(bar(&f) == 2);
}

Alternatively, inside the scope of an aggregate, only the member name is needed:

struct Foo   // or class
{
int a,b,c;
int bar()
{
asm
{
mov EBX, this   ;
mov EAX, b[EBX] ;
}
}
}
void main()
{
Foo f = Foo(0, 2, 0);
assert(f.bar() == 2);
}

Stack Variables

Stack variables (variables local to a function and allocated on the stack) are accessed via the name of the variable indexed by EBP:

int foo(int x)
{
asm
{
mov EAX,x[EBP] ; // loads value of parameter x into EAX
mov EAX,x      ; // does the same thing
}
}

If the [EBP] is omitted, it is assumed for local variables. If naked is used, this no longer holds.

Special Symbols

$

Represents the program counter of the start of the next instruction. So,

jmp  $  ;

branches to the instruction following the jmp instruction. The $ can only appear as the target of a jmp or call instruction.

__LOCAL_SIZE

This gets replaced by the number of local bytes in the local stack frame. It is most handy when the naked is invoked and a custom stack frame is programmed.

Opcodes Supported

Opcodes

aaa

aad

aam

aas

adc

add

addpd

addps

addsd

addss

and

andnpd

andnps

andpd

andps

arpl

bound

bsf

bsr

bswap

bt

btc

btr

bts

call

cbw

cdq

clc

cld

clflush

cli

clts

cmc

cmova

cmovae

cmovb

cmovbe

cmovc

cmove

cmovg

cmovge

cmovl

cmovle

cmovna

cmovnae

cmovnb

cmovnbe

cmovnc

cmovne

cmovng

cmovnge

cmovnl

cmovnle

cmovno

cmovnp

cmovns

cmovnz

cmovo

cmovp

cmovpe

cmovpo

cmovs

cmovz

cmp

cmppd

cmpps

cmps

cmpsb

cmpsd

cmpss

cmpsw

cmpxchg

cmpxchg8b

cmpxchg16b

comisd

comiss

cpuid

cvtdq2pd

cvtdq2ps

cvtpd2dq

cvtpd2pi

cvtpd2ps

cvtpi2pd

cvtpi2ps

cvtps2dq

cvtps2pd

cvtps2pi

cvtsd2si

cvtsd2ss

cvtsi2sd

cvtsi2ss

cvtss2sd

cvtss2si

cvttpd2dq

cvttpd2pi

cvttps2dq

cvttps2pi

cvttsd2si

cvttss2si

cwd

cwde

da

daa

das

db

dd

de

dec

df

di

div

divpd

divps

divsd

divss

dl

dq

ds

dt

dw

emms

enter

f2xm1

fabs

fadd

faddp

fbld

fbstp

fchs

fclex

fcmovb

fcmovbe

fcmove

fcmovnb

fcmovnbe

fcmovne

fcmovnu

fcmovu

fcom

fcomi

fcomip

fcomp

fcompp

fcos

fdecstp

fdisi

fdiv

fdivp

fdivr

fdivrp

feni

ffree

fiadd

ficom

ficomp

fidiv

fidivr

fild

fimul

fincstp

finit

fist

fistp

fisub

fisubr

fld

fld1

fldcw

fldenv

fldl2e

fldl2t

fldlg2

fldln2

fldpi

fldz

fmul

fmulp

fnclex

fndisi

fneni

fninit

fnop

fnsave

fnstcw

fnstenv

fnstsw

fpatan

fprem

fprem1

fptan

frndint

frstor

fsave

fscale

fsetpm

fsin

fsincos

fsqrt

fst

fstcw

fstenv

fstp

fstsw

fsub

fsubp

fsubr

fsubrp

ftst

fucom

fucomi

fucomip

fucomp

fucompp

fwait

fxam

fxch

fxrstor

fxsave

fxtract

fyl2x

fyl2xp1

hlt

idiv

imul

in

inc

ins

insb

insd

insw

int

into

invd

invlpg

iret

iretd

iretq

ja

jae

jb

jbe

jc

jcxz

je

jecxz

jg

jge

jl

jle

jmp

jna

jnae

jnb

jnbe

jnc

jne

jng

jnge

jnl

jnle

jno

jnp

jns

jnz

jo

jp

jpe

jpo

js

jz

lahf

lar

ldmxcsr

lds

lea

leave

les

lfence

lfs

lgdt

lgs

lidt

lldt

lmsw

lock

lods

lodsb

lodsd

lodsw

loop

loope

loopne

loopnz

loopz

lsl

lss

ltr

maskmovdqu

maskmovq

maxpd

maxps

maxsd

maxss

mfence

minpd

minps

minsd

minss

mov

movapd

movaps

movd

movdq2q

movdqa

movdqu

movhlps

movhpd

movhps

movlhps

movlpd

movlps

movmskpd

movmskps

movntdq

movnti

movntpd

movntps

movntq

movq

movq2dq

movs

movsb

movsd

movss

movsw

movsx

movupd

movups

movzx

mul

mulpd

mulps

mulsd

mulss

neg

nop

not

or

orpd

orps

out

outs

outsb

outsd

outsw

packssdw

packsswb

packuswb

paddb

paddd

paddq

paddsb

paddsw

paddusb

paddusw

paddw

pand

pandn

pavgb

pavgw

pcmpeqb

pcmpeqd

pcmpeqw

pcmpgtb

pcmpgtd

pcmpgtw

pextrw

pinsrw

pmaddwd

pmaxsw

pmaxub

pminsw

pminub

pmovmskb

pmulhuw

pmulhw

pmullw

pmuludq

pop

popa

popad

popf

popfd

por

prefetchnta

prefetcht0

prefetcht1

prefetcht2

psadbw

pshufd

pshufhw

pshuflw

pshufw

pslld

pslldq

psllq

psllw

psrad

psraw

psrld

psrldq

psrlq

psrlw

psubb

psubd

psubq

psubsb

psubsw

psubusb

psubusw

psubw

punpckhbw

punpckhdq

punpckhqdq

punpckhwd

punpcklbw

punpckldq

punpcklqdq

punpcklwd

push

pusha

pushad

pushf

pushfd

pxor

rcl

rcpps

rcpss

rcr

rdmsr

rdpmc

rdtsc

rep

repe

repne

repnz

repz

ret

retf

rol

ror

rsm

rsqrtps

rsqrtss

sahf

sal

sar

sbb

scas

scasb

scasd

scasw

seta

setae

setb

setbe

setc

sete

setg

setge

setl

setle

setna

setnae

setnb

setnbe

setnc

setne

setng

setnge

setnl

setnle

setno

setnp

setns

setnz

seto

setp

setpe

setpo

sets

setz

sfence

sgdt

shl

shld

shr

shrd

shufpd

shufps

sidt

sldt

smsw

sqrtpd

sqrtps

sqrtsd

sqrtss

stc

std

sti

stmxcsr

stos

stosb

stosd

stosw

str

sub

subpd

subps

subsd

subss

syscall

sysenter

sysexit

sysret

test

ucomisd

ucomiss

ud2

unpckhpd

unpckhps

unpcklpd

unpcklps

verr

verw

wait

wbinvd

wrmsr

xadd

xchg

xlat

xlatb

xor

xorpd

xorps

Pentium 4 (Prescott) Opcodes Supported

Pentium 4 Opcodes

addsubpd

addsubps

fisttp

haddpd

haddps

hsubpd

hsubps

lddqu

monitor

movddup

movshdup

movsldup

mwait

AMD Opcodes Supported

AMD Opcodes

pavgusb

pf2id

pfacc

pfadd

pfcmpeq

pfcmpge

pfcmpgt

pfmax

pfmin

pfmul

pfnacc

pfpnacc

pfrcp

pfrcpit1

pfrcpit2

pfrsqit1

pfrsqrt

pfsub

pfsubr

pi2fd

pmulhrw

pswapd

SIMD

SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2 and AVX are supported.

GCC syntax

The GNU D Compiler uses an alternative, GCC-based syntax for inline assembler:

GccAsmStatement:
asm FunctionAttributesopt { GccAsmInstructionList }

GccAsmInstructionList:
GccAsmInstruction ;
GccAsmInstruction ; GccAsmInstructionList

GccAsmInstruction:
GccBasicAsmInstruction
GccExtAsmInstruction
GccGotoAsmInstruction

GccBasicAsmInstruction:
GccAsmStringExpression

GccExtAsmInstruction:
GccAsmStringExpression : GccAsmOperandsopt
GccAsmStringExpression : GccAsmOperandsopt : GccAsmOperandsopt
GccAsmStringExpression : GccAsmOperandsopt : GccAsmOperandsopt : GccAsmClobbersopt

GccGotoAsmInstruction:
GccAsmStringExpression : : GccAsmOperandsopt : GccAsmClobbersopt : GccAsmGotoLabelsopt

GccAsmStringExpression:
StringLiteral
( ConditionalExpression )

GccAsmOperands:
GccSymbolicNameopt GccAsmStringExpression ( AssignExpression )
GccSymbolicNameopt GccAsmStringExpression ( AssignExpression ) , GccAsmOperands

GccSymbolicName:
[ Identifier ]

GccAsmClobbers:
GccAsmStringExpression
GccAsmStringExpression , GccAsmClobbers

GccAsmGotoLabels:
Identifier
Identifier , GccAsmGotoLabels

Embedded Documentation

The D programming language enables embedding both contracts and test code along side the actual code, which helps to keep them all consistent with each other. One thing lacking is the documentation, as ordinary comments are usually unsuitable for automated extraction and formatting into manual pages. Embedding the user documentation into the source code has important advantages, such as not having to write the documentation twice, and the likelihood of the documentation staying consistent with the code.

Some existing approaches to this are:

- Doxygen which already has some support for D

- Java's Javadoc, probably the most well-known

- C#'s embedded XML

- Other documentation tools

D's goals for embedded documentation are:

1. It looks good as embedded documentation, not just after it is extracted and processed.

2. It's easy and natural to write, i.e. minimal reliance on <tags> and other clumsy forms one would never see in a finished document.

3. It does not repeat information that the compiler already knows from parsing the code.

4. It doesn't rely on embedded HTML, as such will impede extraction and formatting for other purposes.

5. It's based on existing D comment forms, so it is completely independent of parsers only interested in D code.

6. It should look and feel different from code, so it won't be visually confused with code.

7. It should be possible for the user to use Doxygen or other documentation extractor if desired.

Specification

The specification for the form of embedded documentation comments only specifies how information is to be presented to the compiler. It is implementation-defined how that information is used and the form of the final presentation. Whether the final presentation form is an HTML web page, a man page, a PDF file, etc. is not specified as part of the D Programming Language.

Phases of Processing

Embedded documentation comments are processed in a series of phases:

1. Lexical - documentation comments are identified and attached to tokens.

2. Parsing - documentation comments are associated with specific declarations and combined.

3. Sections - each documentation comment is divided up into a sequence of sections.

4. Special sections are processed.

5. Highlighting of non-special sections is done.

6. All sections for the module are combined.

7. Macro and Escape text substitution is performed to produce the final result.

Lexical

Embedded documentation comments are one of the following forms:

1. /** ... */ The two *'s after the opening /

2. /++ ... +/ The two +'s after the opening /

3. /// The three slashes

The following are all embedded documentation comments:

/// This is a one line documentation comment.

/** So is this. */

/++ And this. +/

/**
This is a brief documentation comment.
*/

/**
* The leading * on this line is not part of the documentation comment.
*/

/*********************************
The extra *'s immediately following the /** are not
part of the documentation comment.
*/

/++
This is a brief documentation comment.
+/

/++
+ The leading + on this line is not part of the documentation comment.
+/

/+++++++++++++++++++++++++++++++++
The extra +'s immediately following the / ++ are not
part of the documentation comment.
+/

/**************** Closing *'s are not part *****************/

The extra *'s and +'s on the comment opening, closing and left margin are ignored and are not part of the embedded documentation. Comments not following one of those forms are not documentation comments.

Parsing

Each documentation comment is associated with a declaration. If the documentation comment is on a line by itself or with only whitespace to the left, it refers to the next declaration. Multiple documentation comments applying to the same declaration are concatenated. Documentation comments not associated with a declaration are ignored. Documentation comments preceding the ModuleDeclaration apply to the entire module. If the documentation comment appears on the same line to the right of a declaration, it applies to that.

If a documentation comment for a declaration consists only of the identifier ditto then the documentation comment for the previous declaration at the same declaration scope is applied to this declaration as well.

If there is no documentation comment for a declaration, that declaration may not appear in the output. To ensure it does appear in the output, put an empty declaration comment for it.

int a;  /// documentation for a; b has no documentation
int b;

/** documentation for c and d */
/** more documentation for c and d */
int c;
/** ditto */
int d;

/** documentation for e and f */ int e;
int f;  /// ditto

/** documentation for g */
int g; /// more documentation for g

/// documentation for C and D
class C
{
int x; /// documentation for C.x

/** documentation for C.y and C.z */
int y;
int z; /// ditto
}

/// ditto
class D { }

Sections

The document comment is a series of Sections. A Section is a name that is the first non-blank character on a line immediately followed by a ':'. This name forms the section name. The section name is not case-sensitive.

Section names starting with 'http://' or 'https://' are not recognized as section names.

Summary

The first section is the Summary, and does not have a section name. It is first paragraph, up to a blank line or a section name. While the summary can be any length, try to keep it to one line. The Summary section is optional.

Description

The next unnamed section is the Description. It consists of all the paragraphs following the Summary until a section name is encountered or the end of the comment.

While the Description section is optional, there cannot be a Description without a Summary section.

/***********************************
* Brief summary of what
* myfunc does, forming the summary section.
*
* First paragraph of synopsis description.
*
* Second paragraph of
* synopsis description.
*/

void myfunc() { }

Named sections follow the Summary and Description unnamed sections.

Standard Sections

For consistency and predictability, there are several standard sections. None of these are required to be present.

Authors:

Lists the author(s) of the declaration.

/** * Authors: Melvin D. Nerd, melvin@mailinator.com */
Bugs:

Lists any known bugs.

/** * Bugs: Doesn't work for negative values. */
Date:

Specifies the date of the current revision. The date should be in a form parseable by std.date.

/** * Date: March 14, 2003 */
Deprecated:

Provides an explanation for and corrective action to take if the associated declaration is marked as deprecated.

/** * Deprecated: superseded by function bar(). */ deprecated void foo() { ... }
Examples:

Any usage examples

/** * Examples: * -------------------- * writeln("3"); // writes '3' to stdout * -------------------- */
History:

Revision history.

/** * History: * V1 is initial version * * V2 added feature X */
License:

Any license information for copyrighted code.

/** * License: use freely for any purpose */ void bar() { ... }
Returns:

Explains the return value of the function. If the function returns void, don't redundantly document it.

/** * Read the file. * Returns: The contents of the file. */ void[] readFile(const(char)[] filename) { ... }
See_Also:

List of other symbols and URLs to related items.

/** * See_Also: * foo, bar, http://www.digitalmars.com/d/phobos/index.html */
Standards:

If this declaration is compliant with any particular standard, the description of it goes here.

/** * Standards: Conforms to DSPEC-1234 */
Throws:

Lists exceptions thrown and under what circumstances they are thrown.

/** * Write the file. * Throws: WriteException on failure. */ void writeFile(string filename) { ... }
Version:

Specifies the current version of the declaration.

/** * Version: 1.6a */

Special Sections

Some sections have specialized meanings and syntax.

Copyright:

This contains the copyright notice. The macro COPYRIGHT is set to the contents of the section when it documents the module declaration. The copyright section only gets this special treatment when it is for the module declaration.

/** Copyright: Public Domain */ module foo;
Params:

Function parameters can be documented by listing them in a params section. Each line that starts with an identifier followed by an '=' starts a new parameter description. A description can span multiple lines.

/*********************************** * foo does this. * Params: * x = is for this * and not for that * y = is for that */ void foo(int x, int y) { }
Macros:

The macros section follows the same syntax as the Params: section. It's a series of NAME=value pairs. The NAME is the macro name, and value is the replacement text.

/** * Macros: * FOO = now is the time for * all good men * BAR = bar * MAGENTA = <font color="magenta">$0</font> */

Highlighting

Embedded Comments

The documentation comments can themselves be commented using the $(DDOC_COMMENT comment text) syntax. These comments do not nest.

Embedded Code

D code can be embedded using lines beginning with at least three hyphens -, backticks ` or tildes ~ (ignoring whitespace) to delineate the code section:

/++
+ Our function.
+
+ Example:
+ ---
+ import std.stdio;
+
+ void foo()
+ {
+     writeln("foo!");  /* print the string */
+ }
+ ---
+/

Note that in the above example the documentation comment uses the /++ ... +/ form so that /* ... */ can be used inside the code section.

D code gets automatic syntax highlighting. To include code in another language without syntax highlighting, add a language string at the end of the top delimiter line:

/++
+ Some C++
+ ``` cpp
+ #include <iostream>
+
+ void foo()
+ {
+     std::cout << "foo!";
+ }
+ ```
+/

Inline Code

Inline code can be written between backtick characters (`), similarly to the syntax used on GitHub, Reddit, Stack Overflow, and other websites. Both the opening and closing ` character must appear on the same line to trigger this behavior. Note that macros are still expanded inside backticks. See also escaping.

Text inside these sections will be escaped according to the rules described above, then wrapped in a $(DDOC_BACKQUOTED) macro. By default, this macro expands to be displayed as an inline text span, formatted as code.

A literal backtick character can be output either as a non-paired ` on a single line or by using the $(BACKTICK) macro.

/// Returns `true` if `a == b`.
void foo() {}

/// Backquoted `<html>` will be displayed to the user instead
/// of passed through as embedded HTML (see below).
void bar() {}

Embedded HTML

HTML can be embedded into the documentation comments, and it will be passed through to the HTML output unchanged. However, since it is not necessarily true that HTML will be the desired output format of the embedded documentation comment extractor, it is best to avoid using it where practical.

/**
* Example of embedded HTML:
*
* <ol>
*   <li><a href="http://www.digitalmars.com">Digital Mars</a></li>
*   <li><a href="http://www.classicempire.com">Empire</a></li>
* </ol>
*/

Headings

A long documentation section can be subdivided by adding headings. A heading is a line of text that starts with one to six # characters followed by whitespace and then the heading text. The number of # characters determines the heading level. Headings may optionally end with any number of trailing # characters.

/**
* # H1
* ## H2
* ### H3
* #### H4 ###
* ##### H5 ##
* ###### H6 #
*/

Links

Documentation may link to other documentation or to a URL. There are four styles of links:

/**
* Some links:
*
* 1. A [reference link][ref] and bare reference links: [ref] or [Object]
* 2. An [inline link](https://dlang.org)
* 3. A bare URL: https://dlang.org
* 4. An ![image](https://dlang.org/images/d3.png)
*
* [ref]: https://dlang.org "The D Language Website"
*/

Reference Links

Reference-style links enclose a reference label in square brackets. They may optionally be preceded by some link text, also enclosed in square brackets.

The reference label must match a reference defined elsewhere. This may be a D symbol in scope of the source code being documented, like [Object] in the example above, or it may be an explicit reference that is defined in the same documentation comment, like [ref] in the example above. In the example both instances of [ref] in item 1. will be replaced with the URL and title text from the matching definition at the bottom of the example. The first link will read reference link and the second will read ref.

Reference definitions start with a label in square brackets, followed by a colon, a URL and an optional title wrapped in single or double quotes, or in parentheses. If a reference label would match both a D symbol and a reference definition then the reference definition is used.

The generated links to D symbols are relative if they have the same root package as the module being documented. If not, their URLs are preceded by a $(DDOC_ROOT_pkg) macro, where pkg is the root package of the symbol being linked to. Links to D symbols are generated with a $(DOC_EXTENSION) macro after the module name. Then the generated URL for [Object] in the above example is as if it had been written:

$(DOC_ROOT_object)object$(DOC_EXTENSION)#.Object

DOC_ROOT_ macros can be defined for any external packages to link to using a Macros section.

Inline Links

Inline-style links enclose link text in square brackets and the link URL in parentheses. Like reference links, the URL may optionally be followed by title text wrapped in single or double quotes, or in parentheses:

/// [a link with title text](https://dlang.org 'Some title text')

Bare URLs

Bare URLs are sequences of characters that start with http:// or https://, continue with one or more characters from the set of letters, digits and -_?=%&/+#~., and contain at least one period. URL recognition happens before all macro text substitution. The URL is wrapped in a $(DDOC_LINK_AUTODETECT) macro and is otherwise left untouched.

Images

Images have the same form as reference or inline links, but add an exclamation point ! before the initial square bracket. What would be the link text in a normal link is used as the image's alt text.

Lists

Documentation may contain lists. Start an ordered list with a number followed by a period:

/**
* 1. First this
* 2. Then this
*    1. A sub-item
*/

Start an unordered list with a hyphen (-), an asterisk (*) or a plus (+). Subsequent items in the same list must also start with the same symbol:

/**
* - A list
* - With a second item
*
* + A different list
*   - With a sub-item
*
* * A third list (note the double asterisks)
*/

Note the double asterisks in the example above. This is because the list is inside a documentation comment that is delimited with asterisks, so the initial asterisk is considered part of the documentation comment, not a list item. This is even true when other lines don't start with an asterisk:

/**
- A list
* Not a list because the asterisk is part of the documentation comment
*/

/++
+ + The caveat also applies to plus-delimited documentation comments
+/

List items can include content like new paragraphs, headings, embedded code, or child list items. Simply indent the content to match the indent of the text after the list symbol:

/**
* - A parent list item
*
*   With a second paragraph
*
*   - A sub-item
*     ---
*     // A code example inside the sub-item
*     ---
*/

Tables

Data may be placed into a table. Tables consist of a single header row, a delimiter row, and zero or more data rows. Cells in each row are separated by pipe (|) characters. Initial and trailing |'s are optional. The number of cells in the delimiter row must match the number of cells in the header row:

/**
*  | Item | Price |
*  | ---- | ----: |
*  | Wigs | $10 |
*    Wheels | $13
*  | Widgets | $200 |
*/

Cells in the delimiter row contain hyphens (-) and optional colons (:). A : to the left of the hyphens creates a left-aligned column, a : to the right of the hyphens creates a right-aligned column (like the example above), and :'s on both sides of the hyphens create a center-aligned column.

Quotes

Documentation may include a section of quoted material by prefixing each line of the section with a >. Quotes may include headings, lists, embedded code, etc.

/**
* > To D, or not to D. -- Willeam NerdSpeare
*/

Lines of text that directly follow a quoted line are considered part of the quote:

/**
* > This line
* and this line are both part of the quote
*
* This line is not part of the quote.
*/

Horizontal Rules

Create a horizontal rule by adding a line containing three or more asterisks, underscores or hyphens:

/**
* ***
* ___
*/

As with lists, note that the initial * in the example above will be stripped because it is part of a documentation comment that is delimited with asterisks. At least three subsequent asterisks are needed.

To create a horizontal rule with hyphens, add spaces between the hyphens. Without the spaces they would be treated as the start or end of an embedded code block. Note that any horizontal rule may contain spaces:

/**
* - - -
* _ _ _
* * * *
*/

Text Emphasis

A span of text wrapped in asterisks (*) is emphasized, and text wrapped in two asterisks (**) is strongly emphasized:

*single asterisks* is rendered as single asterisks.

**double asterisks** is rendered as double asterisks.

Insert a literal asterisk by backslash-escaping it: \* is rendered as *.

Unlike Markdown, underscores (_) are not supported for emphasizing text because it would break snake_case names and underscore prefix processing in identifier emphasis.

Identifier Emphasis

Identifiers in documentation comments that are function parameters or are names that are in scope at the associated declaration are emphasized in the output. This emphasis can take the form of italics, boldface, a hyperlink, etc. How it is emphasized depends on what it is  —  a function parameter, type, D keyword, etc. To prevent unintended emphasis of an identifier, it can be preceded by an underscore (_). The underscore will be stripped from the output.

Character Entities

Some characters have special meaning to the documentation processor, to avoid confusion it can be best to replace them with their corresponding character entities:

Characters and Entities

Character

Entity

<

&lt;

>

&gt;

&

&amp;

It is not necessary to do this inside a code section, or if the special character is not immediately followed by a # or a letter.

Punctuation Escapes

Escape any ASCII punctuation symbol with a backslash \. Doing so outputs the original character without the backslash, except for the following characters which output predefined macros instead:

Characters and Escape Macros

Character

Macro

(

$(LPAREN)

)

$(RPAREN)

,

$(COMMA)

$

$(DOLLAR)

To output a backslash, simply use two backslashes in a row: \\. Note that backslashes inside embedded or inline code do not escape punctuation and are included in the output as-is. Backslashes before non-punctation are also included in the output as-is. For example, C:\dmd2\bin\dmd.exe does not require escaping its embedded backslashes.

No Documentation

No documentation is generated for the following constructs, even if they have a documentation comment:

- Invariants

- Postblits

- Destructors

- Static constructors and static destructors

- Class info, type info, and module info

Macros

The documentation comment processor includes a simple macro text preprocessor. When $(NAME) appears in section text it is replaced with the corresponding NAME macro's replacement text. Macros can take arguments: $(NAME argument).
For example:

/**
Macros:
PARAM = <u>$1</u>
MATH_DOCS = <a href="https://dlang.org/phobos/std_math.html">Math Docs</a>
*/
module math;

/**
* This function returns the sum of $(PARAM a) and $(PARAM b).
* See also the $(MATH_DOCS).
*/
int sum(int a, int b) { return a + b; }

The above would generate output such as:

<h1>math</h1>
<dl><dt><big><a name="sum"></a>int <u>sum</u>(int <i>a</i>, int <i>b</i>);
</big></dt>
<dd>This function returns the <u>sum</u> of <u><i>a</i></u> and <u><i>b</i></u>.
See also the <a href="https://dlang.org/phobos/std_math.html">Math Docs</a>.
</dd>
</dl>

The replacement text is recursively scanned for more macros. If found, they are expanded in turn. If a macro already expanded is recursively encountered, with no argument or with the same argument text as the enclosing macro, it is replaced with no text.

- Macro invocations that cut across replacement text boundaries are not expanded.

- If the macro name is undefined, the replacement text will be $(DDOC_UNDEFINED_MACRO(NAME)). This defaults to empty.

- If $(NAME) is required to exist in the output without being macro expanded, the $ can be backslash-escaped: \$.

Macro Arguments

When invoking a macro, any text from the end of the identifier to the closing ‘)’ is passed as arguments to the macro, and can be referred to using the $0 parameter inside the macro definition. A $0 in the replacement text is replaced with the text of each argument, separated by commas.

If there are commas in the argument text, this denotes multiple arguments. Inside a macro definition, $1 will represent the argument text up to the first comma, $2 from the first comma to the second comma, etc., up to $9. $+ represents the text from the first comma to the closing ‘)’.

- The argument text can contain nested parentheses, "" or '' strings, <!-- ... --> comments, or tags.

- If stray, unnested parentheses are used, they can be backslash-escaped: \( or \).

- Any literal commas not intended as an argument separator can be escaped when invoking a macro expecting separate arguments. Defining an ARGS=$0 macro can be useful to handle commas - these are equivalent:

- $(FOO one, $(ARGS two, dwa, dos), three)

- $(FOO one, two\, dwa\, dos, three).

Macro Definitions

Macro definitions come from the following sources, in the specified order:

1. Predefined macros.

2. Definitions from file specified by sc.ini's or dmd.conf DDOCFILE setting.

3. Definitions from *.ddoc files specified on the command line.

4. Runtime definitions generated by Ddoc.

5. Definitions from any Macros: sections.

Macro redefinitions replace previous definitions of the same name. This means that the sequence of macro definitions from the various sources forms a hierarchy.

Macro names beginning with "D_" and "DDOC_" are reserved.

Predefined Macros

A number of macros are predefined Ddoc, and represent the minimal definitions needed by Ddoc to format and highlight the presentation. The definitions are for simple HTML.

The implementations of all predefined macros are implementation-defined. The reference implementation's macro definitions can be found here.

Ddoc does not generate HTML code. It formats into the basic formatting macros, which (in their predefined form) are then expanded into HTML. If output other than HTML is desired, then these macros need to be redefined.

Predefined Formatting Macros

Name

Description

B

boldface the argument

I

italicize the argument

U

underline the argument

P

argument is a paragraph

DL

argument is a definition list

DT

argument is a definition in a definition list

DD

argument is a description of a definition

TABLE

argument is a table

TR

argument is a row in a table

TH

argument is a header entry in a row

TD

argument is a data entry in a row

OL

argument is an ordered list

UL

argument is an unordered list

LI

argument is an item in a list

BIG

argument is one font size bigger

SMALL

argument is one font size smaller

BR

start new line

LINK

generate clickable link on argument

LINK2

generate clickable link, first arg is address

RED

argument is set to be red

BLUE

argument is set to be blue

GREEN

argument is set to be green

YELLOW

argument is set to be yellow

BLACK

argument is set to be black

WHITE

argument is set to be white

D_CODE

argument is D code

D_INLINECODE

argument is inline D code

LF

Insert a line feed (newline)

LPAREN

Insert a left parenthesis

RPAREN

Insert a right parenthesis

BACKTICK

Insert a backtick

DOLLAR

Insert a dollar sign

DDOC

overall template for output

ESCAPES

characters to substitute

DDOC is special in that it specifies the boilerplate into which the entire generated text is inserted (represented by the Ddoc generated macro BODY). For example, in order to use a style sheet, DDOC would be redefined as:

DDOC = <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html><head>
<META http-equiv="content-type" content="text/html; charset=utf-8">
<title>$(TITLE)</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head><body>
<h1>$(TITLE)</h1>
$(BODY)
</body></html>

ESCAPES defines a series of substitutions which replace special characters with a string. It's useful when the output format requires escaping of certain characters, for example in HTML & should be escaped with &amp;. The syntax is /c/string/, where c is either a single character, or multiple characters separated by whitespace or commas, and string is the replacement text.

ESCAPES = /&/AddressOf!/
/!/Exclamation/
/?/QuestionMark/
/,/Comma/
/{ }/Parens/
/<,>/Arrows/

Highlighting of D code is performed by the following macros:

D Code Formatting Macros

Name

Description

D_COMMENT

Highlighting of comments

D_STRING

Highlighting of string literals

D_KEYWORD

Highlighting of D keywords

D_PSYMBOL

Highlighting of current declaration name

D_PARAM

Highlighting of current function declaration parameters

The highlighting macros start with DDOC_. They control the formatting of individual parts of the presentation.

Ddoc Section Formatting Macros

Name

Description

DDOC_CONSTRAINT

Highlighting of a template constraint.

DDOC_COMMENT

Inserts a comment in the output.

DDOC_DECL

Highlighting of the declaration.

DDOC_DECL_DD

Highlighting of the description of a declaration.

DDOC_DITTO

Highlighting of ditto declarations.

DDOC_SECTIONS

Highlighting of all the sections.

DDOC_SUMMARY

Highlighting of the summary section.

DDOC_DESCRIPTION

Highlighting of the description section.

DDOC_AUTHORS

Highlighting of the authors section.

DDOC_BUGS

Highlighting of the bugs section.

DDOC_COPYRIGHT

Highlighting of the copyright section.

DDOC_DATE

Highlighting of the date section.

DDOC_DEPRECATED

Highlighting of the deprecated section.

DEPRECATED

Wrapper for deprecated declarations.

DDOC_EXAMPLES

Highlighting of the examples section.

DDOC_HISTORY

Highlighting of the history section.

DDOC_LICENSE

Highlighting of the license section.

DDOC_OVERLOAD_SEPARATOR

Inserts a separator between overloads of a given name.

DDOC_RETURNS

Highlighting of the returns section.

DDOC_SEE_ALSO

Highlighting of the see-also section.

DDOC_STANDARDS

Highlighting of the standards section.

DDOC_THROWS

Highlighting of the throws section.

DDOC_VERSION

Highlighting of the version section.

DDOC_SECTION_H

Highlighting of the section name of a non-standard section.

DDOC_SECTION

Highlighting of the contents of a non-standard section.

DDOC_MEMBERS

Default highlighting of all the members of a class, struct, etc.

DDOC_MODULE_MEMBERS

Highlighting of all the members of a module.

DDOC_CLASS_MEMBERS

Highlighting of all the members of a class.

DDOC_STRUCT_MEMBERS

Highlighting of all the members of a struct.

DDOC_ENUM_MEMBERS

Highlighting of all the members of an enum.

DDOC_TEMPLATE_PARAM

Highlighting of a template's individual parameters.

DDOC_TEMPLATE_PARAM_LIST

Highlighting of a template's parameter list.

DDOC_TEMPLATE_MEMBERS

Highlighting of all the members of a template.

DDOC_ENUM_BASETYPE

Highlighting of the type an enum is based upon

DDOC_PARAMS

Highlighting of a function parameter section.

DDOC_PARAM_ROW

Highlighting of a name=value function parameter.

DDOC_PARAM_ID

Highlighting of the parameter name.

DDOC_PARAM_DESC

Highlighting of the parameter value.

DDOC_BLANKLINE

Inserts a blank line.

DDOC_ANCHOR

Expands to a named anchor used for hyperlinking to a particular declaration section. Argument $1 expands to the qualified declaration name.

DDOC_PSYMBOL

Highlighting of declaration name to which a particular section is referring.

DDOC_PSUPER_SYMBOL

Highlighting of the base type of a class.

DDOC_KEYWORD

Highlighting of D keywords.

DDOC_PARAM

Highlighting of function parameters.

DDOC_BACKQUOTED

Inserts inline code.

DDOC_AUTO_PSYMBOL_SUPPRESS

Highlighting of auto-detected symbol that starts with underscore

DDOC_AUTO_PSYMBOL

Highlighting of auto-detected symbol

DDOC_AUTO_KEYWORD

Highlighting of auto-detected keywords

DDOC_AUTO_PARAM

Highlighting of auto-detected parameters

For example, one could redefine DDOC_SUMMARY:

DDOC_SUMMARY = $(GREEN $0)

And all the summary sections will now be green.

Macro Definitions from sc.ini's DDOCFILE

A text file of macro definitions can be created, and specified in sc.ini:

DDOCFILE=myproject.ddoc

Macro Definitions from .ddoc Files on the Command Line

File names on the DMD command line with the extension .ddoc are text files that are read and processed in order.

Macro Definitions Generated by Ddoc

Generated Macro Definitions

Macro Name

Content

BODY

Set to the generated document text.

TITLE

Set to the module name.

DATETIME

Set to the current date and time.

YEAR

Set to the current year.

COPYRIGHT

Set to the contents of any Copyright: section that is part of the module comment.

DOCFILENAME

Set to the name of the generated output file.

SRCFILENAME

Set to the name of the source file the documentation is being generated from.

Using Ddoc to generate examples from unit tests

Ddoc can automatically generate usage examples for declarations using unit tests. If a declaration is followed by a documented unit test, the code from the test will be inserted into the example section of the declaration. This avoids the frequent problem of having outdated documentation for pieces of code.

To create a documented unit test just add three forward slashes before the unittest block, like this:

///
unittest
{
...
}

For more information please see the full section on documented unit tests.

Using Ddoc for other Documentation

Ddoc is primarily designed for use in producing documentation from embedded comments. It can also, however, be used for processing other general documentation. The reason for doing this would be to take advantage of the macro capability of Ddoc and the D code syntax highlighting capability.

If the .d source file starts with the string "Ddoc" then it is treated as general purpose documentation, not as a D code source file. From immediately after the "Ddoc" string to the end of the file or any "Macros:" section forms the document. No automatic highlighting is done to that text, other than highlighting of D code embedded between lines delineated with --- lines. Only macro processing is done.

Much of the D documentation itself is generated this way, including this page. Such documentation is marked at the bottom as being generated by Ddoc.

Security considerations

Note that DDoc comments may embed raw HTML, including <script> tags. Be careful when publishing or distributing rendered DDoc HTML generated from untrusted sources, as this may allow cross-site scripting.

Links to D documentation generators

A list of current D documentation generators which use Ddoc can be found on our wiki page.

Interfacing to C

D is designed to fit comfortably with a C compiler for the target system. It is able to call C functions directly without requiring wrapper functions.

This is done by matching the C compiler's data types, layouts, and function call/return sequences.

The ImportC compiler extension allows importing or compiling .c files directly.

Bindings for popular C libraries can be found in the standard library and package repository.

The rest of this page describes the manual, low-level side of interfacing with C.

Calling C Functions

C functions can be called directly from D. There is no need for wrapper functions, argument swizzling, and the C functions do not need to be put into a separate DLL.

The C function must be declared and given a calling convention, most likely the "C" calling convention, for example:

extern (C) int strcmp(const char* string1, const char* string2);

and then it can be called within D code in the obvious way:

import std.string;
int myDfunction(char[] s)
{
return strcmp(std.string.toStringz(s), "foo");
}

There are several things going on here:

- D understands how C function names are "mangled" and the correct C function call/return sequence.

- C functions cannot be overloaded with another C function with the same name.

- There are no __cdecl, __far, __stdcall, __declspec, or other such C extended type modifiers in D. These are handled by linkage attributes, such as extern (C).

- There is no volatile type modifier in D. To declare a C function that uses volatile, just drop the keyword from the declaration.

- Strings are not 0 terminated in D. See "Data Type Compatibility" for more information about this. However, string literals in D are 0 terminated.

C code can correspondingly call D functions, if the D functions use an attribute that is compatible with the C compiler, most likely the extern (C):

// myfunc() can be called from any C function
extern (C)
{
void myfunc(int a, int b)
{
...
}
}

Storage Allocation

C code explicitly manages memory with calls to malloc() and free(). D allocates memory using the D garbage collector, so no explicit frees are necessary.

D can still explicitly allocate memory using core.stdc.stdlib.malloc() and core.stdc.stdlib.free(), these are useful for connecting to C functions that expect malloc'd buffers, etc.

If pointers to D garbage collector allocated memory are passed to C functions, it's critical to ensure that the memory will not be collected by the garbage collector before the C function is done with it. This is accomplished by:

- Making a copy of the data using core.stdc.stdlib.malloc() and passing the copy instead.

- Leaving a pointer to it on the stack (as a parameter or automatic variable), as the garbage collector will scan the stack.

- Leaving a pointer to it in the static data segment, as the garbage collector will scan the static data segment.

- Registering the pointer with the garbage collector with the std.gc.addRoot() or std.gc.addRange() calls.

An interior pointer to the allocated memory block is sufficient to let the GC know the object is in use; i.e. it is not necessary to maintain a pointer to the beginning of the allocated memory.

The garbage collector does not scan the stacks of threads not created by the D Thread interface. Nor does it scan the data segments of other DLLs, etc.

Data Type Compatibility

D And C Type Equivalence

D

C

32 bit

64 bit

void

void

byte

signed char

ubyte

unsigned char

char

char (chars are unsigned in D)

wchar

wchar_t (when sizeof(wchar_t) is 2)

dchar

wchar_t (when sizeof(wchar_t) is 4)

short

short

ushort

unsigned short

int

int

uint

unsigned

core.stdc.config.c_long

long

long

core.stdc.config.c_ulong

unsigned long

unsigned long

core.stdc.stdint.intptr_t

intptr_t

intptr_t

core.stdc.stdint.uintptr_t

uintptr_t

uintptr_t

long

long long

long (or long long)

ulong

unsigned long long

unsigned long (or unsigned long long)

float

float

double

double

real

long double

cdouble

double _Complex

creal

long double _Complex

struct

struct

union

union

enum

enum

class

no equivalent

type *

type *

type[dim]

type[dim]

type[dim], type()[dim]

type[dim], type()[dim]

type[]

no equivalent

type1[type2]

no equivalent

type function(params)

type(*)(params)

type delegate(params)

no equivalent

size_t

size_t

ptrdiff_t

ptrdiff_t

These equivalents hold for most C compilers. The C standard does not pin down the sizes of the types, so some care is needed.

Passing D Array Arguments to C Functions

In C, arrays are passed to functions as pointers even if the function prototype says its an array. In D, static arrays are passed by value, not by reference. Thus, the function prototype must be adjusted to match what C expects.

D And C Function Prototype Equivalence

D type

C type

T*

T[]

ref T[dim]

T[dim]

For example:

void foo(int a[3]) { ... } // C code

extern (C)
{
void foo(ref int[3] a); // D prototype
}

Calling printf()

printf can be directly called from D code:

import core.stdc.stdio;

int main()
{
printf("hello world\n");
return 0;
}

Printing values works as it does in C:

int apples;
printf("there are %d apples\n", apples);

Correctly matching the format specifier to the D type is necessary. The D compiler recognizes the printf formats and diagnoses mismatches with the supplied arguments. The specification for the formats used by D is the C99 specification 7.19.6.1.

A generous interpretation of what is a match between the argument and format specifier is taken, for example, an unsigned type can be printed with a signed format specifier. Diagnosed incompatibilites are:

- incompatible sizes which may cause argument misalignment

- dereferencing arguments that are not pointers

- insufficient number of arguments

- struct, array and slice arguments are not allowed

- non-pointer arguments to s specifier

- non-Standard formats

- undefined behavior per C99

Strings

A string cannot be printed directly. But %.*s can be used:

string s = "betty";
printf("hello %.*s\n", cast(int) s.length, s.ptr);

The cast to int is required.

size_t and ptrdiff_t

These use the zu and td format specifiers respectively:

import core.stdc.stdio : printf;
int* p = new int, q = new int;
printf("size of an int is %zu, pointer difference is %td\n", int.sizeof, p - q);

Non-Standard Format Specifiers

Non-Standard format specifiers will be rejected by the compiler. Since the checking is only done for formats as string literals, non-Standard ones can be used:

const char* format = "value: %K\n";
printf(format, value);

Modern Formatted Writing

An improved D function for formatted output is std.stdio.writef().

Structs and Unions

D structs and unions are analogous to C's.

C code often adjusts the alignment and packing of struct members with a command line switch or with various implementation specific #pragmas. D supports explicit alignment attributes that correspond to the C compiler's rules. Check what alignment the C code is using, and explicitly set it for the D struct declaration.

D does not support bit fields. If needed, they can be emulated with shift and mask operations, or use the std.bitmanip.bitfields library type. htod will convert bit fields to inline functions that do the right shift and masks.

D does not support declaring variables of anonymous struct types. In such a case, define a named struct in D and make it private:

union Info  // C code
{
struct
{
char *name;
} file;
};

union Info  // D code
{
private struct File
{
char* name;
}
File file;
}

Callbacks

D can easily call C callbacks (function pointers), and C can call callbacks provided by D code if the callback is an extern(C) function, or some other linkage that both sides have agreed to (e.g. extern(Windows)).

Here's an example of C code providing a callback to D code:

void someFunc(void *arg) { printf("Called someFunc!\n"); }  // C code
typedef void (*Callback)(void *);
extern "C" Callback getCallback(void)
{
return someFunc;
}

extern(C) alias Callback = int function(int, int);  // D code
extern(C) Callback getCallback();
void main()
{
Callback cb = getCallback();
cb();  // invokes the callback
}

And an example of D code providing a callback to C code:

extern "C" void printer(int (*callback)(int, int))  // C code
{
printf("calling callback with 2 and 4 returns: %d\n", callback(2, 4));
}

extern(C) alias Callback = int function(int, int);  // D code
extern(C) void printer(Callback callback);
extern(C) int sum(int x, int y) { return x + y; }
void main()
{
printer(&sum);
}

For more info about callbacks read the closures section.

Using Existing C Libraries

Since D can call C code directly, it can also call any C library functions, giving D access to the smorgasbord of existing C libraries. To do so, however, one needs to write a D interface (.di) file, which is a translation of the C .h header file for the C library into D.

For popular C libraries, the first place to look for the corresponding D interface file is the Deimos Project. If it isn't there already, please write and contribute one to the Deimos Project.

Accessing C Globals

C globals can be accessed directly from D. C globals have the C naming convention, and so must be in an extern (C) block. Use the extern storage class to indicate that the global is allocated in the C code, not the D code. C globals default to being in global, not thread local, storage. To reference global storage from D, use the __gshared storage class.

extern (C) extern __gshared int x;

Interfacing to C++

This document specifies how to interface with C++ directly.

It is also possible to indirectly interface with C++ code, either through a C interface or a COM interface.

The General Idea

Being 100% compatible with C++ means more or less adding a fully functional C++ compiler front end to D. Anecdotal evidence suggests that writing such is a minimum of a 10 man-year project, essentially making a D compiler with such capability unimplementable. Other languages looking to hook up to C++ face the same problem, and the solutions have been:

1. Support the COM interface (but that only works for Windows).

2. Laboriously construct a C wrapper around the C++ code.

3. Use an automated tool such as SWIG to construct a C wrapper.

4. Reimplement the C++ code in the other language.

5. Give up.

D takes a pragmatic approach that assumes a couple modest accommodations can solve a significant chunk of the problem:

- matching C++ name mangling conventions

- matching C++ function calling conventions

- matching C++ virtual function table layout for single inheritance

Platform-Compiler Support

- On Windows, prefer linking D code to MSVC compiled binaries.

- On Linux, linking to both GCC and Clang compiled binaries is supported.

- On MacOS, prefer linking to Clang compiled binaries.

Global Functions

C++ global functions, including those in namespaces, can be declared and called in D, or defined in D and called in C++.

Calling C++ Global Functions from D

Given a C++ function in a C++ source file:

#include <iostream>

using namespace std;

int foo(int i, int j, int k)
{
cout << "i = " << i << endl;
cout << "j = " << j << endl;
cout << "k = " << k << endl;

return 7;
}

In the corresponding D code, foo is declared as having C++ linkage and function calling conventions:

extern (C++) int foo(int i, int j, int k);

and then it can be called within the D code:

extern (C++) int foo(int i, int j, int k);

void main()
{
foo(1, 2, 3);
}

Compiling the two files, the first with a C++ compiler, the second with a D compiler, linking them together, and then running it yields:

> g++ -c foo.cpp
> dmd bar.d foo.o -L-lstdc++ && ./bar
i = 1
j = 2
k = 3

There are several things going on here:

- D understands how C++ function names are "mangled" and the correct C++ function call/return sequence.

- Because modules are not part of C++, each function with C++ linkage in the global namespace must be globally unique within the program.

- There are no __cdecl, __far, __stdcall, __declspec, or other such nonstandard C++ extensions in D.

- There are no volatile type modifiers in D.

- Strings are not 0 terminated in D. See "Data Type Compatibility" for more information about this. However, string literals in D are 0 terminated.

Calling Global D Functions From C++

To make a D function accessible from C++, give it C++ linkage:

import std.stdio;

extern (C++) int foo(int i, int j, int k)
{
writefln("i = %s", i);
writefln("j = %s", j);
writefln("k = %s", k);
return 1;
}

extern (C++) void bar();

void main()
{
bar();
}

The C++ end looks like:

int foo(int i, int j, int k);

void bar()
{
foo(6, 7, 8);
}

Compiling, linking, and running produces the output:

> dmd -c foo.d
> g++ bar.cpp foo.o -lphobos2 -pthread -o bar && ./bar
i = 6
j = 7
k = 8

C++ Namespaces

C++ symbols that reside in namespaces can be accessed from D. A namespace can be added to the extern (C++) LinkageAttribute:

extern (C++, N) int foo(int i, int j, int k);

void main()
{
N.foo(1, 2, 3);   // foo is in C++ namespace 'N'
}

C++ can open the same namespace in the same file and multiple files. In D, this can be done as follows:

module ns;
extern (C++, `ns`)
{
int foo() { return 1; }
}

Any expression that resolves to either a tuple of strings or an empty tuple is accepted. When the expression resolves to an empty tuple, it is equivalent to extern (C++)

extern(C++, (expression))
{
int bar() { return 2; }
}

or in multiple files, by organizing them in a package consisting of several modules:

ns/
|-- a.d
|-- b.d
|-- package.d

File ns/a.d:

module a; extern (C++, `ns`) { int foo() { return 1; } }

File ns/b.d:

module b; extern (C++, `ns`) { int bar() { return 2; } }

File ns/package.d:

module ns;
public import a, b;

Then import the package containing the extern C++ declarations as follows:

import ns;
static assert(foo() == 1 && bar() == 2);

Note that the extern (C++, ns) linkage attribute affects only the ABI (name mangling and calling convention) of these declarations. Importing them follows the usual D module import semantics.

Alternatively, the non-string form can be used to introduce a scope. Note that the enclosing module already provides a scope for the symbols declared in the namespace. This form does not allow closing and reopening the same namespace with in the same module. That is:

module a; extern (C++, ns1) { int foo() { return 1; } }

module b; extern (C++, ns1) { int bar() { return 2; } }

import a, b;
static assert(foo() == 1 && bar() == 2);

works, but:

extern (C++, ns1) { int foo() { return 1; } }
extern (C++, ns1) { int bar() { return 2; } }

does not. Additionally, aliases can be used to avoid collision of symbols:

module a; extern (C++, ns) { int foo() { return 1; } }

module b; extern (C++, ns) { int bar() { return 2; } }

module ns;
import a, b;
alias foo = a.ns.foo;
alias bar = b.ns.bar;

import ns;
static assert(foo() == 1 && bar() == 2);

Classes

C++ classes can be declared in D by using the extern (C++) attribute on class, struct and interface declarations. extern (C++) interfaces have the same restrictions as D interfaces, which means that Multiple Inheritance is supported to the extent that only one base class can have member fields.

extern (C++) structs do not support virtual functions but can be used to map C++ value types.

Unlike classes and interfaces with D linkage, extern (C++) classes and interfaces are not rooted in Object and cannot be used with typeid.

D structs and classes have different semantics whereas C++ structs and classes are basically the same. The use of a D struct or class depends on the C++ implementation and not on the used C++ keyword.

- If the C++ implementation has one or more virtual member functions (including inherited member functions), it should be declared as an extern(C++) class in D.

- If the C++ implementation does not have any virtual member functions (including inherited member functions), it should be declared as an extern(C++) struct in D.

When mapping a D class onto a C++ struct, use extern(C++, struct) to avoid linking problems with C++ compilers (notably MSVC) that distinguish between C++'s class and struct when mangling. Conversely, use extern(C++, class) to map a D struct onto a C++ class.

extern(C++, class) and extern(C++, struct) can be combined with C++ namespaces:

extern (C++, struct) extern (C++, foo)
class Bar
{
}

Using C++ Classes From D

The following example shows binding of a pure virtual function, its implementation in a derived class, a non-virtual member function, and a member field:

#include <iostream>

using namespace std;

class Base
{
public:
virtual void print3i(int a, int b, int c) = 0;
};

class Derived : public Base
{
public:
int field;
Derived(int field) : field(field) {}

void print3i(int a, int b, int c)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}

int mul(int factor);
};

int Derived::mul(int factor)
{
return field * factor;
}

Derived *createInstance(int i)
{
return new Derived(i);
}

void deleteInstance(Derived *&d)
{
delete d;
d = 0;
}

We can use it in D code like:

extern(C++)
{
abstract class Base
{
void print3i(int a, int b, int c);
}

class Derived : Base
{
int field;
@disable this();
override void print3i(int a, int b, int c);
final int mul(int factor);
}

Derived createInstance(int i);
void deleteInstance(ref Derived d);
}

void main()
{
import std.stdio;

auto d1 = createInstance(5);
writeln(d1.field);
writeln(d1.mul(4));

Base b1 = d1;
b1.print3i(1, 2, 3);

deleteInstance(d1);
assert(d1 is null);

auto d2 = createInstance(42);
writeln(d2.field);

deleteInstance(d2);
assert(d2 is null);
}

Compiling, linking, and running produces the output:

> g++ base.cpp
> dmd main.d base.o -L-lstdc++ && ./main
5
20
a = 1
b = 2
c = 3
42

Note how in the above example, the constructor is not bindable and is instead disabled on the D side; an alternative would be to reimplement the constructor in D. See the section below on lifetime management for more information.

Using D Classes From C++

Given D code like:

extern (C++) int callE(E);

extern (C++) interface E
{
int bar(int i, int j, int k);
}

class F : E
{
extern (C++) int bar(int i, int j, int k)
{
import std.stdio : writefln;
writefln("i = %s", i);
writefln("j = %s", j);
writefln("k = %s", k);
return 8;
}
}

void main()
{
F f = new F();
callE(f);
}

The C++ code to access it looks like:

class E
{
public:
virtual int bar(int i, int j, int k);
};

int callE(E *e)
{
return e->bar(11, 12, 13);
}

> dmd -c base.d
> g++ klass.cpp base.o -lphobos2 -pthread -o klass && ./klass
i = 11
j = 12
k = 13

Structs

C++ allows a struct to inherit from a base struct. This is done in D using alias this:

struct Base { ... members ... };

struct Derived
{
Base base;       // make it the first field
alias base this;

... members ...
}

In both C++ and D, if a struct has zero fields, the struct still has a size of 1 byte. But, in C++ if the struct with zero fields is used as a base struct, its size is zero (called the Empty Base Optimization). There are two methods for emulating this behavior in D. The first forwards references to a function returning a faked reference to the base:

struct Base { ... members ... };

struct DerivedStruct
{
static if (Base.tupleof.length > 0)
Base base;
else
ref inout(Base) base() inout
{
return *cast(inout(Base)*)&this;
}
alias base this;

... members ...
}

The second makes use of template mixins:

mixin template BaseMembers()
{
void memberFunction() { ... }
}

struct Base
{
mixin BaseMembers!();
}

struct Derived
{
mixin BaseMembers!();

... members ...
}

Note that the template mixin is evaluated in the context of its instantiation, not declaration. If this is a problem, the template mixin can use local imports, or have the member functions forward to the actual functions.

C++ Templates

C++ function and type templates can be bound by using the extern (C++) attribute on a function or type template declaration.

Note that all instantiations used in D code must be provided by linking to C++ object code or shared libraries containing the instantiations.

For example:

#include <iostream>

template<class T>
struct Foo
{
private:
T field;

public:
Foo(T t) : field(t) {}
T get();
void set(T t);
};

template<class T>
T Foo<T>::get()
{
return field;
}

template<class T>
void Foo<T>::set(T t)
{
field = t;
}

Foo<int> makeIntFoo(int i)
{
return Foo<int>(i);
}

Foo<char> makeCharFoo(char c)
{
return Foo<char>(c);
}

template<class T>
void increment(Foo<T> &foo)
{
foo.set(foo.get() + 1);
}

template<class T>
void printThreeNext(Foo<T> foo)
{
for(size_t i = 0; i < 3; ++i)
{
std::cout << foo.get() << std::endl;
increment(foo);
}
}

// The following two functions ensure that the required instantiations of
// printThreeNext are provided by this code module
void printThreeNexti(Foo<int> foo)
{
printThreeNext(foo);
}

void printThreeNextc(Foo<char> foo)
{
printThreeNext(foo);
}

extern(C++):
struct Foo(T)
{
private:
T field;

public:
@disable this();
T get();
void set(T t);
}

Foo!int makeIntFoo(int i);
Foo!char makeCharFoo(char c);
void increment(T)(ref Foo!T foo);
void printThreeNext(T)(Foo!T foo);

extern(D) void main()
{
auto i = makeIntFoo(42);
assert(i.get() == 42);
i.set(1);
increment(i);
assert(i.get() == 2);

auto c = makeCharFoo('a');
increment(c);
assert(c.get() == 'b');

c.set('A');
printThreeNext(c);
}

Compiling, linking, and running produces the output:

> g++ -c template.cpp
> dmd main.d template.o -L-lstdc++ && ./main
A
B
C

Function Overloading

C++ and D follow different rules for function overloading. D source code, even when calling extern (C++) functions, will still follow D overloading rules.

Memory Allocation

C++ code explicitly manages memory with calls to ::operator new() and ::operator delete(). D's new operator allocates memory using the D garbage collector, so no explicit delete is necessary. D's new operator is not compatible with C++'s ::operator new and ::operator delete. Attempting to allocate memory with D's new and deallocate with C++ ::operator delete will result in miserable failure.

D can explicitly manage memory using a variety of library tools, such as with std.experimental.allocator. Additionally, core.stdc.stdlib.malloc and core.stdc.stdlib.free can be used directly for connecting to C++ functions that expect malloc'd buffers.

If pointers to memory allocated on the D garbage collector heap are passed to C++ functions, it's critical to ensure that the referenced memory will not be collected by the D garbage collector before the C++ function is done with it. This is accomplished by:

- Making a copy of the data using std.experimental.allocator or core.stdc.stdlib.malloc and passing the copy instead.

- Leaving a pointer to it on the stack (as a parameter or automatic variable), as the garbage collector will scan the stack.

- Leaving a pointer to it in the static data segment, as the garbage collector will scan the static data segment.

- Registering the pointer with the garbage collector using the core.memory.GC.addRoot or core.memory.GC.addRange functions.

An interior pointer to the allocated memory block is sufficient to let the GC know the object is in use; i.e. it is not necessary to maintain a pointer to the beginning of the allocated memory.

The garbage collector does not scan the stacks of threads not registered with the D runtime, nor does it scan the data segments of shared libraries that aren't registered with the D runtime.

Data Type Compatibility

D And C++ Type Equivalence

D type

C++ type

void

void

byte

signed char

ubyte

unsigned char

char

char (chars are unsigned in D)

core.stdc.stddef.wchar_t

wchar_t

short

short

ushort

unsigned short

int

int

uint

unsigned

long

long if it is 64 bits wide, otherwise long long

ulong

unsigned long if it is 64 bits wide, otherwise unsigned long long

core.stdc.config.cpp_long

long

core.stdc.config.cpp_ulong

unsigned long

float

float

double

double

real

long double

extern (C++) struct

struct or class without virtual member functions

extern (C++) class

struct or class with virtual member functions

extern (C++) interface

struct or class with no member fields

union

union

enum

enum

type*

type *

ref type (in parameter lists only)

type &

type[dim]

type[dim] for a variable/field declaration, or use ref for function parameter

type[dim]*

type(*)[dim]

type[]

no extern (C++) equivalent, see below

type[type]

no equivalent

type function(parameters)

type(*)(parameters)

type delegate(parameters)

no equivalent

These equivalents hold when the D and C++ compilers used are companions on the host platform.

Dynamic Arrays

These are not supported for extern (C++). For extern (C), they are equivalent to a struct template. For example:

extern (C) const(char)[] slice;

dmd -HC generates the following C++ declaration:

extern "C" _d_dynamicArray< const char > slice;

_d_dynamicArray is generated as follows:

/// Represents a D [] array
template<typename T>
struct _d_dynamicArray final
{
size_t length;
T *ptr;

_d_dynamicArray() : length(0), ptr(NULL) { }

_d_dynamicArray(size_t length_in, T *ptr_in)
: length(length_in), ptr(ptr_in) { }

T& operator[](const size_t idx) {
assert(idx < length);
return ptr[idx];
}

const T& operator[](const size_t idx) const {
assert(idx < length);
return ptr[idx];
}
};

Packing and Alignment

D structs and unions are analogous to C's.

C code often adjusts the alignment and packing of struct members with a command line switch or with various implementation specific #pragmas. D supports explicit alignment attributes that correspond to the C compiler's rules. Check what alignment the C code is using, and explicitly set it for the D struct declaration.

D supports bitfields in the standard library: see std.bitmanip.bitfields.

Lifetime Management

C++ constructors, copy constructors, move constructors and destructors cannot be called directly in D code, and D constructors, postblit operators and destructors cannot be directly exported to C++ code. Interoperation of types with these special operators is possible by either 1) disabling the operator in the client language and only using it in the host language, or 2) faithfully reimplementing the operator in the client language. With the latter approach, care needs to be taken to ensure observable semantics remain the same with both implementations, which can be difficult, or in some edge cases impossible, due to differences in how the operators work in the two languages. For example, in D all objects are movable and there is no move constructor.

Operator Overloading

D's operator overloading is primarily involved with overloading its unary and binary operators including the increment, index, slice, cast, function call, comparison operator etc. Overloads of these operators that exist in C++ can be directly called from D provided the D operator and its C++ counterpart semantically agree. Below is a simple use case involving the assignment, index, and function call operator overloads interoperating between D and C++.

In a foo.cpp file:

#include <iostream>

class A
{
public:
int a;
int b;
int buffer[3];
A()
{
a = 0;
b = 0;
}
A(int a, int b);
A& operator=(const A& obj); //overloading the assignment operator
void operator()(int a, int b, int c); // overloading the call operator
int& operator[](int value); // overloading the index operator
void printbuffer();
};

A::A(int a, int b)
{
this->a = a;
this->b = b;
}

A& A::operator=(const A& obj)
{
a = obj.a;
b = obj.b;
return *this;
}

void A::operator()(int a, int b, int c)
{
std::cout << "value of a is "<< a << std::endl;
std::cout << "value of b is "<< b << std::endl;
std::cout << "value of c is "<< c << std::endl;
}

int& A::operator[](int value)
{
return buffer[value];
}

void A::printbuffer()
{
for (int i = 0; i < 3; i++)
{
std::cout << "the buffer numbers of index "<< i << " is "<< buffer[i] << std:: endl;
}
}

In a bar.d file:

import std.stdio;

extern(C++) struct A
{
int a;
int b;
int[3] buffer;
this(int a, int b);
ref A opAssign(ref const A); // links with A::operator=
void opCall(int a, int b, int c); // links with A::operator()
ref int opIndex(int value); // links with A::operator[]
void printbuffer();
}

void main()
{
auto obj1 = A(1, 3);
auto obj2 = A(13, 43);
obj2 = obj1; // opAssign called here
writeln("obj2's a is ", obj2.a);
writeln("obj2's b is ", obj2.b);
obj2(20, 40, 60);
obj2[0] = 30;
obj2[1] = 60;
obj2[2] = 90;
obj2.printbuffer();
}

Compiling, linking, and running produces the output:

> g++ -c foo.cpp
> dmd bar.d foo.o -L-lstdc++ && ./bar
obj2's a is 1
obj2's b is 3
value of a is 20
value of b is 40
value of c is 60
the buffer numbers of index 0 is 30
the buffer numbers of index 1 is 60
the buffer numbers of index 2 is 90

Runtime Type Identification

D runtime type identification uses completely different techniques than C++. The two are incompatible.

Exception Handling

Exception interoperability is a work in progress.

At present, C++ exceptions cannot be caught in or thrown from D, and D exceptions cannot be caught in or thrown from C++. Additionally, objects in C++ stack frames are not guaranteed to be destroyed when unwinding the stack due to a D exception, and vice versa.

The plan is to support all of the above except throwing D exceptions directly in C++ code (but they will be throwable indirectly by calling into a D function with C++ linkage).

Comparing D Immutable and Const with C++ Const

Const, Immutable Comparison

Feature

D

C++98

const keyword

Yes

Yes

immutable keyword

Yes

No

const notation

// Functional:
//ptr to const ptr to const int
const(int*)* p;

// Postfix:
//ptr to const ptr to const int
const int *const *p;

transitive const

// Yes:
//const ptr to const ptr to const int
const int** p;
**p = 3; // error

// No:
// const ptr to ptr to int
int** const p;
**p = 3;    // ok

cast away const

// Yes:
// ptr to const int
const(int)* p;
int* q = cast(int*)p; // ok

// Yes:
// ptr to const int
const int* p;
int* q = const_cast<int*>p; //ok

cast+mutate

// No:
// ptr to const int
const(int)* p;
int* q = cast(int*)p;
*q = 3;   // undefined behavior

// Yes:
// ptr to const int
const int* p;
int* q = const_cast<int*>p;
*q = 3;   // ok

overloading

// Yes:
void foo(int x);
void foo(const int x);  //ok

// No:
void foo(int x);
void foo(const int x);  //error

const/mutable aliasing

// Yes:
void foo(const int* x, int* y)
{
bar(*x); // bar(3)
*y = 4;
bar(*x); // bar(4)
}
...
int i = 3;
foo(&i, &i);

// Yes:
void foo(const int* x, int* y)
{
bar(*x); // bar(3)
*y = 4;
bar(*x); // bar(4)
}
...
int i = 3;
foo(&i, &i);

immutable/mutable aliasing

// No:
void foo(immutable int* x, int* y)
{
bar(*x); // bar(3)
*y = 4;  // undefined behavior
bar(*x); // bar(??)
}
...
int i = 3;
foo(cast(immutable)&i, &i);

No immutables

type of string literal

immutable(char)[]

const char*

string literal to non-const

not allowed

allowed, but deprecated

Interfacing to Objective-C

D supports interfacing with Objective-C. It supports protocols, classes, subclasses, instance variables, instance methods and class methods. Platform support might vary between different compilers.

Fully working example is available at the bottom.

Classes

Declaring an External Class

import core.attribute : selector;

extern (Objective-C)
extern class NSString
{
const(char)* UTF8String() @selector("UTF8String");
}

All Objective-C classes that should be accessible from within D need to be declared with the Objective-C linkage. If the class is declared as extern (in addition to extern (Objective-C)) it is expected to be defined externally.

The @selector attribute indicates which Objective-C selector should be used when calling this method. This attribute needs to be attached to all methods with the Objective-C linkage.

Binding to a @property (Accessor Methods)

import core.attribute : selector;

extern (Objective-C)
extern class MTLRenderPipelineDescriptor : NSObject
{
NSString label() @selector("label");
NSString label(NSString) @selector("setLabel:");
}

Whenever needing to bind to Objective-C classes @property, one must be aware that it generates both a getter and setter. The method to get its value (getter) is the same name as the property's name. The method to set its value (setter) starts with the word "set" and then uses the capitalized property name. The setter for the property label is setLabel:.

Defining a Class

import core.attribute : selector;

// externally defined
extern (Objective-C)
extern class NSObject
{
static NSObject alloc() @selector("alloc");
NSObject init() @selector("init");
}

extern (Objective-C)
class Foo : NSObject
{
override static Foo alloc() @selector("alloc");
override Foo init() @selector("init");

final int bar(int a) @selector("bar:")
{
return a;
}
}

void main()
{
assert(Foo.alloc.init.bar(3) == 3);
}

Defining an Objective-C class is exactly the same as declaring an external class but it should not be declared as extern.

To match the Objective-C semantics, static and final methods are virtual. static methods are overridable as well.

Protocols

Declaring a Protocol

import core.attribute : selector;
import core.stdc.stdio : printf;

extern (Objective-C)
interface Foo
{
static void foo() @selector("foo");
void bar() @selector("bar");
}

extern (Objective-C)
class Bar : Foo
{
static void foo() @selector("foo")
{
printf("foo\n");
}

void bar() @selector("bar")
{
printf("bar\n");
}
}

Objective-C protocols are represented as interfaces in D and are declared using the interface keyword.

All Objective-C protocols that should be accessible from within D need to be declared with the Objective-C linkage.

Objective-C protocols support virtual class (static) methods. These methods must be implemented by the class that implements the protocol (unless they are optional). To match these semantics, static methods are virtual. That also means that static methods with Objective-C linkage, inside an interface cannot have a body.

Optional Methods

import core.attribute : optional, selector;
import core.stdc.stdio : printf;

struct objc_selector;
alias SEL = objc_selector*;

extern (C) SEL sel_registerName(in char* str);

extern (Objective-C)
extern class NSObject
{
static NSObject alloc() @selector("alloc");
NSObject init() @selector("init");
}

extern (Objective-C)
interface Foo
{
bool respondsToSelector(SEL sel) @selector("respondsToSelector:");
void foo() @selector("foo");

// this is an optional method
@optional void bar() @selector("bar");
}

extern (Objective-C)
class Bar : NSObject, Foo
{
override static Bar alloc() @selector("alloc");
override Bar init() @selector("init");

bool respondsToSelector(SEL sel) @selector("respondsToSelector:");

void foo() @selector("foo")
{
printf("foo\n");
}
}

void main()
{
Foo f = Bar.alloc.init;

// check, at runtime, if the instance `f` implements the method `bar`
if (f.respondsToSelector(sel_registerName("bar")))
f.bar();
else
f.foo();
}

Objective-C protocols support optional methods. Optional methods are not required to be implemented by the class that implements the protocol. To safely call an optional method, a runtime check should be performed to make sure the receiver implements the method.

In D, optional methods are represented using the @optional attribute.

Instance Variables

import core.attribute : selector;

// externally defined
extern (Objective-C)
extern class NSObject
{
static NSObject alloc() @selector("alloc");
NSObject init() @selector("init");
}

extern (Objective-C)
class Foo : NSObject
{
int bar_;

override static Foo alloc() @selector("alloc");
override Foo init() @selector("init");

int bar() @selector("bar")
{
return bar_;
}
}

void main()
{
auto foo = Foo.alloc.init;
foo.bar_ = 3;
assert(foo.bar == 3);
}

Declaring an instance variable looks exactly the same as for a regular D class.

To solve the fragile base class problem, instance variables in Objective-C has a dynamic offset. That means that the base class can change (add or remove instance variables) without the subclasses needing to recompile or relink. Thanks to this feature it's not necessary to declare instance variables when creating bindings to Objective-C classes.

Calling an Instance Method

Calling an Objective-C instance method uses the same syntax as calling regular D methods:

const(char)* result = object.UTF8String();

When the compiler sees a call to a method with Objective-C linkage it will generate a call similar to how an Objective-C compiler would call the method.

The @selector Attribute

The @selector attribute is a compiler recognized UDA. It is used to tell the compiler which selector to use when calling an Objective-C method.

Selectors in Objective-C can contain the colon character, which is not valid in D identifiers. D supports method overloading while Objective-C achieves something similar by using different selectors. For these two reasons it is better to be able to specify the selectors manually in D, instead of trying to infer it. This allows to have a more natural names for the methods in D. Example:

import core.attribute : selector;

extern (Objective-C)
extern class NSString
{
NSString initWith(in char*) @selector("initWithUTF8String:");
NSString initWith(NSString) @selector("initWithString:");
}

Here the method initWith is overloaded with two versions, one accepting in char*, the other one NSString. These two methods are mapped to two different Objective-C selectors, initWithUTF8String: and initWithString:.

The attribute is defined in druntime in core.attribute. The attribute is only defined when the version identifier D_ObjectiveC is enabled.

Compiler Checks

The compiler performs the following checks to enforce the correct usage of the @selector attribute:

- The attribute can only be attached to methods with Objective-C linkage

- The attribute can only be attached once to a method

- The attribute cannot be attached to a template method

- The number of colons in the selector needs to match the number of parameters the method is declared with

If any of the checks fail, a compile error will occur.

The @optional Attribute

The @optional attribute is a compiler recognized UDA. It is used to tell the compiler that a method, with Objective-C linkage, declared inside an interface is optional. That means that the class that implements the interface does not have to implement the method.

To safely call an optional method, a runtime check should be performed to make sure the receiver implements the method.

The attribute is defined in druntime in core.attribute. The attribute is only defined when the version identifier D_ObjectiveC is enabled.

Compiler Checks

The compiler performs the following checks to enforce the correct usage of the @optional attribute:

- The attribute can only be attached to methods with Objective-C linkage

- The attribute can only be attached to a method inside an interface

- The attribute can only be attached once to a method

- The attribute cannot be attached to a template method

If any of the checks fail, a compile error will occur.

The D_ObjectiveC Version Identifier

The D_ObjectiveC version identifier is a predefined version identifier. It is enabled if Objective-C support is available for the target.

Objective-C Linkage

Objective-C linkage is achieved by attaching the extern (Objective-C) attribute to a class. Example:

import core.attribute : selector;

extern (Objective-C)
extern class NSObject
{
NSObject init() @selector("init");
}

All methods inside a class declared as extern (Objective-C) will get implicit Objective-C linkage.

The linkage is recognized on all platforms but will issue a compile error if it is used on a platform where Objective-C support is not available. This allows to easily hide Objective-C declarations from platforms where it is not available using the version statement, without resorting to string mixins or other workarounds.

Memory Management

The preferred way to do memory management in Objective-C is to use Automatic Reference Counting, ARC. This is not supported in D, therefore manual memory management is required to be used instead. This is achieved by calling release on an Objective-C instance, like in the old days of Objective-C.

Frameworks

Most Objective-C code is bundled in something called a "Framework". This is basically a regular directory, with the .framework extension and a specific directory layout. A framework contains a dynamic library, all public header files and any resources (images, sounds and so on) required by the framework.

These directories are recognized by some tools, like the Objective-C compiler and linker, to be frameworks. To link with a framework from DMD, use the following flags:

-L-framework -L<Framework>

where <Framework> is the name of the framework to link with, without the .framework extension. The two -L flags are required because the linker expects a space between the -framework flag and the name of the framework. DMD cannot handle this and will instead interpret the name of the framework as a separate flag.
Framework Paths

Using the above flag, the linker will search in the standard framework paths. The standard search paths for frameworks are:

- /System/Library/Frameworks

- /Library/Frameworks

The following flag from DMD can be used to add a new path in which to search for frameworks:

-L-F<framework_path>

For more information see the reference documentation and the ld man page.

Full Usage Example

This example will create an Objective-C string, NSString, and log the message using NSLog to stderr.

import core.attribute : selector;

extern (Objective-C)
extern class NSString
{
static NSString alloc() @selector("alloc");
NSString initWithUTF8String(in char* str) @selector("initWithUTF8String:");
void release() @selector("release");
}

This is a simplified declaration of the NSString class. The alloc method allocates an instance of the class. The initWithUTF8String: method will be used to convert a C string in UTF-8 to an Objective-C string, NSString. The release method is used to release an deallocate the string. Since D doesn't support ARC it's needed to manually release Objective-C instances.

extern (C) void NSLog(NSString, ...);

This NSLog function prints a message to the System Log facility, i.e. to stderr and Console.

auto str = NSString.alloc();

Allocate an instance of the class, NSString.

str = str.initWithUTF8String("Hello World!")

Initialize the Objective-C string using a C string.

NSLog(str);

Log the string to stderr, this will print something like this in the terminal:

2015-07-18 13:14:27.978 main[11045:2934950] Hello World!

str.release();

Release and deallocate the string.

All steps combined look like this:

module main;

import core.attribute : selector;

extern (Objective-C)
extern class NSString
{
static NSString alloc() @selector("alloc");
NSString initWithUTF8String(in char* str) @selector("initWithUTF8String:");
void release() @selector("release");
}

extern (C) void NSLog(NSString, ...);

void main()
{
auto str = NSString.alloc().initWithUTF8String("Hello World!");
NSLog(str);
str.release();
}

When compiling the application remember to link with the required libraries, in this case the Foundation framework. Example:

dmd -L-framework -LFoundation main.d

Portability Guide

It's good software engineering practice to minimize gratuitous portability problems in the code. Techniques to minimize potential portability problems are:

- The integral and floating type sizes should be considered as minimums. Algorithms should be designed to continue to work properly if the type size increases.

- Floating point computations can be carried out at a higher precision than the size of the floating point variable can hold. Floating point algorithms should continue to work properly if precision is arbitrarily increased.

- Avoid depending on the order of side effects in a computation that may get reordered by the compiler. For example:

a + b + c

can be evaluated as (a + b) + c, a + (b + c), (a + c) + b, (c + b) + a, etc. Parentheses control operator precedence, parentheses do not control order of evaluation.

If the operands of an associative operator + or * are floating point values, the expression is not reordered.

- Avoid dependence on byte order; i.e. whether the CPU is big-endian or little-endian.

- Avoid dependence on the size of a pointer or reference being the same size as a particular integral type.

- If size dependencies are inevitable, put a static assert in the code to verify it:

static assert(int.sizeof == (int*).sizeof);

32 to 64 Bit Portability

32 bit processors and operating systems are still out there. With that in mind:

- Integral types will remain the same sizes between 32 and 64 bit code.

- Pointers and object references will increase in size from 4 bytes to 8 bytes going from 32 to 64 bit code.

- Use size_t as an alias for an unsigned integral type that can span the address space. Array indices should be of type size_t.

- Use ptrdiff_t as an alias for a signed integral type that can span the address space. A type representing the difference between two pointers should be of type ptrdiff_t.

- The .length, .size, .sizeof, .offsetof and .alignof properties will be of type size_t.

Endianness

Endianness refers to the order in which multibyte types are stored. The two main orders are big endian and little endian. The compiler predefines the version identifier BigEndian or LittleEndian depending on the order of the target system. The x86 systems are all little endian.

The times when endianness matters are:

- When reading data from an external source (like a file) written in a different endian format.

- When reading or writing individual bytes of a multibyte type like longs or doubles.

OS Specific Code

System specific code is handled by isolating the differences into separate modules. At compile time, the correct system specific module is imported.

Minor differences can be handled by constant defined in a system specific import, and then using that constant in an IfStatement or StaticIfStatement.

Named Character Entities

NamedCharacterEntity:
& Identifier ;

The full list of named character entities from the HTML 5 Spec is supported. Named entities which contain multiple code points can only be used in string literals, not in character literals, since they do not fit in any character type. Below is a partial list of the named character entities.

Note: Not all glyphs will display properly in the Glyph column in all browsers.

Named Character Entities

Name

Value

Glyph

quot

34

"

amp

38

&

lt

60

<

gt

62

>

OElig

338

Œ

oelig

339

œ

Scaron

352

Š

scaron

353

š

Yuml

376

ÿ

circ

710

ˆ

tilde

732

˜

ensp

8194

emsp

8195

thinsp

8201

zwnj

8204

‌

zwj

8205

‍

lrm

8206

‎

rlm

8207

‏

ndash

8211

–

mdash

8212

—

lsquo

8216

‘

rsquo

8217

’

sbquo

8218

‚

ldquo

8220

“

rdquo

8221

”

bdquo

8222

„

dagger

8224

†

Dagger

8225

‡

permil

8240

‰

lsaquo

8249

‹

rsaquo

8250

›

euro

8364

€

Latin-1 (ISO-8859-1) Entities

Name

Value

Glyph

nbsp

160

iexcl

161

¡

cent

162

¢

pound

163

£

curren

164

¤

yen

165

¥

brvbar

166

¦

sect

167

§

uml

168

¨

copy

169

©

ordf

170

ª

laquo

171

«

not

172

¬

shy

173

reg

174

®

macr

175

¯

deg

176

°

plusmn

177

±

sup2

178

²

sup3

179

³

acute

180

´

micro

181

µ

para

182

¶

middot

183

·

cedil

184

¸

sup1

185

¹

ordm

186

º

raquo

187

»

frac14

188

¼

frac12

189

½

frac34

190

¾

iquest

191

¿

Agrave

192

À

Aacute

193

Á

Acirc

194

Â

Atilde

195

Ã

Auml

196

Ä

Aring

197

Å

AElig

198

Æ

Ccedil

199

Ç

Egrave

200

È

Eacute

201

É

Ecirc

202

Ê

Euml

203

Ë

Igrave

204

Ì

Iacute

205

Í

Icirc

206

Î

Iuml

207

Ï

ETH

208

Ð

Ntilde

209

Ñ

Ograve

210

Ò

Oacute

211

Ó

Ocirc

212

Ô

Otilde

213

Õ

Ouml

214

Ö

times

215

×

Oslash

216

Ø

Ugrave

217

Ù

Uacute

218

Ú

Ucirc

219

Û

Uuml

220

Ü

Yacute

221

Ý

THORN

222

Þ

szlig

223

ß

agrave

224

à

aacute

225

á

acirc

226

â

atilde

227

ã

auml

228

ä

aring

229

å

aelig

230

æ

ccedil

231

ç

egrave

232

è

eacute

233

é

ecirc

234

ê

euml

235

ë

igrave

236

ì

iacute

237

í

icirc

238

î

iuml

239

ï

eth

240

ð

ntilde

241

ñ

ograve

242

ò

oacute

243

ó

ocirc

244

ô

otilde

245

õ

ouml

246

ö

divide

247

÷

oslash

248

ø

ugrave

249

ù

uacute

250

ú

ucirc

251

û

uuml

252

ü

yacute

253

ý

thorn

254

þ

yuml

255

ÿ

Symbols and Greek letter entities

Name

Value

Glyph

fnof

402

ƒ

Alpha

913

Α

Beta

914

Β

Gamma

915

Γ

Delta

916

Δ

Epsilon

917

Ε

Zeta

918

Ζ

Eta

919

Η

Theta

920

Θ

Iota

921

Ι

Kappa

922

Κ

Lambda

923

Λ

Mu

924

Μ

Nu

925

Ν

Xi

926

Ξ

Omicron

927

Ο

Pi

928

Π

Rho

929

Ρ

Sigma

931

Σ

Tau

932

Τ

Upsilon

933

Υ

Phi

934

Φ

Chi

935

Χ

Psi

936

Ψ

Omega

937

Ω

alpha

945

α

beta

946

β

gamma

947

γ

delta

948

δ

epsilon

949

ε

zeta

950

ζ

eta

951

η

theta

952

θ

iota

953

ι

kappa

954

κ

lambda

955

λ

mu

956

μ

nu

957

ν

xi

958

ξ

omicron

959

ο

pi

960

π

rho

961

ρ

sigmaf

962

ς

sigma

963

σ

tau

964

τ

upsilon

965

υ

phi

966

φ

chi

967

χ

psi

968

ψ

omega

969

ω

thetasym

977

ϑ

upsih

978

ϒ

piv

982

ϖ

bull

8226

•

hellip

8230

…

prime

8242

′

Prime

8243

″

oline

8254

‾

frasl

8260

⁄

weierp

8472

℘

image

8465

ℑ

real

8476

ℜ

trade

8482

™

alefsym

8501

ℵ

larr

8592

←

uarr

8593

↑

rarr

8594

→

darr

8595

↓

harr

8596

↔

crarr

8629

↵

lArr

8656

⇐

uArr

8657

⇑

rArr

8658

⇒

dArr

8659

⇓

hArr

8660

⇔

forall

8704

∀

part

8706

∂

exist

8707

∃

empty

8709

∅

nabla

8711

∇

isin

8712

∈

notin

8713

∉

ni

8715

∋

prod

8719

∏

sum

8721

∑

minus

8722

−

lowast

8727

∗

radic

8730

√

prop

8733

∝

infin

8734

∞

ang

8736

∠

and

8743

∧

or

8744

∨

cap

8745

∩

cup

8746

∪

int

8747

∫

there4

8756

∴

sim

8764

∼

cong

8773

≅

asymp

8776

≈

ne

8800

≠

equiv

8801

≡

le

8804

≤

ge

8805

≥

sub

8834

⊂

sup

8835

⊃

nsub

8836

⊄

sube

8838

⊆

supe

8839

⊇

oplus

8853

⊕

otimes

8855

⊗

perp

8869

⊥

sdot

8901

⋅

lceil

8968

⌈

rceil

8969

⌉

lfloor

8970

⌊

rfloor

8971

⌋

loz

9674

◊

spades

9824

♠

clubs

9827

♣

hearts

9829

♥

diams

9830

♦

lang

10216

⟨

rang

10217

⟩

Memory Safety

Memory Safety for a program is defined as it being impossible for the program to corrupt memory. Therefore, the safe subset of D consists only of programming language features that are guaranteed to never result in memory corruption. See this article for a rationale.

Memory-safe code cannot use certain language features, such as:

- Casts that break the type system.

- Modification of pointer values.

- Taking the address of a local variable or function parameter.

Usage

There are three categories of functions from the perspective of memory safety:

- @safe functions

- @trusted functions

- @system functions

@system functions may perform any operation legal from the perspective of the language, including inherently memory unsafe operations such as pointer casts or pointer arithmetic. However, compile-time known memory-corrupting operations, such as indexing a static array out of bounds or returning a pointer to an expired stack frame, can still raise an error. @system functions may not be called directly from @safe functions.

@trusted functions must have a safe interface, as they can be called from @safe functions. Internally they have all the capabilities of @system functions. For this reason they should be very limited in the scope of their use. Typical uses of @trusted functions include wrapping system calls that take buffer pointer and length arguments separately so that @safe functions may call them with arrays.

@safe functions have a number of restrictions on what they may do and are intended to disallow operations that may cause memory corruption. See @safe functions.

The @safe attribute can be inferred when the compiler has the function body available, such as with templates.

Array bounds checks are necessary to enforce memory safety, so these are enabled (by default) for @safe code even in -release mode.

Scope and Return Parameters

The function parameter attributes return and scope are used to track what happens to low-level pointers passed to functions. Such pointers include: raw pointers, arrays, this, classes, ref parameters, delegate/lazy parameters, and aggregates containing a pointer.

The return ref attributes on a parameter indicate:

- A pointer derived from the parameter's address may be returned from the function (e.g. returning the parameter by reference).

- A pointer derived from the parameter's address may be stored in the first parameter of a function returning void, iff the first parameter is ref. The this reference is considered the first parameter of a method.

scope ensures that no references to the pointed-to object are retained, in global variables or pointers passed to the function (and recursively to other functions called in the function), as a result of calling the function. Variables in the function body and parameter list that are scope may have their allocations elided as a result.

The return scope attributes on a parameter indicate:

- A pointer derived from that parameter may be returned from the function.

- A pointer derived from that parameter may be stored in the first parameter of a function returning void, iff the first parameter is ref. The this reference is considered the first parameter of a method.

Rationale: The second case is to support UFCS, property setters and non-member functions (e.g. put used like put(dest, source)).

These attributes may appear after the formal parameter list of a method, in which case they apply to the this reference. For constructors, return applies to the (implicitly returned) this reference.

return or scope is ignored when applied to a type that is not a low-level pointer.

Note: Checks for scope parameters are currently enabled only for @safe code compiled with the -dip1000 command-line flag.

Limitations

Memory safety does not imply that code is portable, uses only sound programming practices, is free of byte order dependencies, or other bugs. It is focussed only on eliminating memory corruption possibilities.

Application Binary Interface

A D implementation that conforms to the D ABI (Application Binary Interface) will be able to generate libraries, DLLs, etc., that can interoperate with D binaries built by other implementations.

C ABI

The C ABI referred to in this specification means the C Application Binary Interface of the target system. C and D code should be freely linkable together, in particular, D code shall have access to the entire C ABI runtime library.

Endianness

The endianness (byte order) of the layout of the data will conform to the endianness of the target machine. The Intel x86 CPUs are little endian meaning that the value 0x0A0B0C0D is stored in memory as: 0D 0C 0B 0A.

Basic Types

bool

8 bit byte with the values 0 for false and 1 for true

byte

8 bit signed value

ubyte

8 bit unsigned value

short

16 bit signed value

ushort

16 bit unsigned value

int

32 bit signed value

uint

32 bit unsigned value

long

64 bit signed value

ulong

64 bit unsigned value

cent

128 bit signed value

ucent

128 bit unsigned value

float

32 bit IEEE 754 floating point value

double

64 bit IEEE 754 floating point value

real

implementation defined floating point value, for x86 it is 80 bit IEEE 754 extended real

char

8 bit unsigned value

wchar

16 bit unsigned value

dchar

32 bit unsigned value

Delegates

Delegates are fat pointers with two parts:

Delegate Layout

offset

property

contents

0

.ptr

context pointer

ptrsize

.funcptr

pointer to function

The context pointer can be a class this reference, a struct this pointer, a pointer to a closure (nested functions) or a pointer to an enclosing function's stack frame (nested functions).

Structs and Unions

These conform to the target's C ABI struct layout, except:

- An extern(D) struct or union with no fields has size 1, not 0. See Struct Layout.

- Unions are name-mangled as if they were structs.

- The rest of this document treats unions extrinsically the same as structs.

Classes

An object consists of:

Class Object Layout

size

property

contents

ptrsize

.__vptr

pointer to vtable

ptrsize

.__monitor

monitor

ptrsize...

vptrs for any interfaces implemented by this class in left to right, most to least derived, order

...

...

super's non-static fields and super's interface vptrs, from least to most derived

...

named fields

non-static fields

The vtable consists of:

Virtual Function Pointer Table Layout

size

contents

ptrsize

pointer to instance of TypeInfo

ptrsize...

pointers to virtual member functions

Casting a class object to an interface consists of adding the offset of the interface's corresponding vptr to the address of the base of the object. Casting an interface ptr back to the class type it came from involves getting the correct offset to subtract from it from the object.Interface entry at vtbl[0]. Adjustor thunks are created and pointers to them stored in the method entries in the vtbl[] in order to set the this pointer to the start of the object instance corresponding to the implementing method.

An adjustor thunk looks like:

ADD EAX,offset
JMP method

The leftmost side of the inheritance graph of the interfaces all share their vptrs, this is the single inheritance model. Every time the inheritance graph forks (for multiple inheritance) a new vptr is created and stored in the class' instance. Every time a virtual method is overridden, a new vtbl[] must be created with the updated method pointers in it.

The class definition:

class XXXX
{
....
};

Generates the following:

- An instance of Class called ClassXXXX.

- A type called StaticClassXXXX which defines all the static members.

- An instance of StaticClassXXXX called StaticXXXX for the static members.

Interfaces

An interface is a pointer to a pointer to a vtbl[]. The vtbl[0] entry is a pointer to the corresponding instance of the object.Interface class. The rest of the vtbl[1..$] entries are pointers to the virtual functions implemented by that interface, in the order that they were declared.

A COM interface differs from a regular interface in that there is no object.Interface entry in vtbl[0]; the entries vtbl[0..$] are all the virtual function pointers, in the order that they were declared. This matches the COM object layout used by Windows.

A C++ interface differs from a regular interface in that it matches the layout of a C++ class using single inheritance on the target machine.

Arrays

A dynamic array consists of:

Dynamic Array Layout

offset

property

contents

0

.length

array dimension

size_t

.ptr

pointer to array data

A dynamic array is declared as:

type[] array;

whereas a static array is declared as:

type[dimension] array;

Thus, a static array always has the dimension statically available as part of the type, and so it is implemented like in C. Static arrays and Dynamic arrays can be easily converted back and forth to each other.

Associative Arrays

Associative arrays consist of a pointer to an opaque, implementation defined type.

The current implementation is contained in and defined by rt/aaA.d.

Reference Types

D has reference types, but they are implicit. For example, classes are always referred to by reference; this means that class instances can never reside on the stack or be passed as function parameters.

Name Mangling

D accomplishes typesafe linking by mangling a D identifier to include scope and type information.

MangledName:
_D QualifiedName Type
_D QualifiedName Z        // Internal

The Type above is the type of a variable or the return type of a function. This is never a TypeFunction, as the latter can only be bound to a value via a pointer to a function or a delegate.

QualifiedName:
SymbolFunctionName
SymbolFunctionName QualifiedName

SymbolFunctionName:
SymbolName
SymbolName TypeFunctionNoReturn
SymbolName M TypeModifiersopt TypeFunctionNoReturn

The M means that the symbol is a function that requires a this pointer. Class or struct fields are mangled without M. To disambiguate M from being a Parameter with modifier scope, the following type needs to be checked for being a TypeFunction.

SymbolName:
LName
TemplateInstanceName
IdentifierBackRef
0                         // anonymous symbols

Template Instance Names have the types and values of its parameters encoded into it:

TemplateInstanceName:
TemplateID LName TemplateArgs Z

TemplateID:
__T
__U        // for symbols declared inside template constraint

TemplateArgs:
TemplateArg
TemplateArg TemplateArgs

TemplateArg:
TemplateArgX
H TemplateArgX

If a template argument matches a specialized template parameter, the argument is mangled with prefix H.

TemplateArgX:
T Type
V Type Value
S QualifiedName
X Number ExternallyMangledName

ExternallyMangledName can be any series of characters allowed on the current platform, e.g. generated by functions with C++ linkage or annotated with pragma(mangle,...).

Values:
Value
Value Values

Value:
n
i Number
N Number
e HexFloat
c HexFloat c HexFloat
CharWidth Number _ HexDigits
A Number Values
S Number Values
f MangledName

HexFloat:
NAN
INF
NINF
N HexDigits P Exponent
HexDigits P Exponent

Exponent:
N Number
Number

HexDigits:
HexDigit
HexDigit HexDigits

HexDigit:
Digit
A
B
C
D
E
F

CharWidth:
a
w
d

n

is for null arguments.

i Number

is for positive numeric literals (including character literals).

N Number

is for negative numeric literals.

e HexFloat

is for real and imaginary floating point literals.

c HexFloat c HexFloat

is for complex floating point literals.

CharWidth Number _ HexDigits

CharWidth is whether the characters are 1 byte (a), 2 bytes (w) or 4 bytes (d) in size. Number is the number of characters in the string. The HexDigits are the hex data for the string.

A Number Values

An array or asssociative array literal. Number is the length of the array. Value is repeated Number times for a normal array, and 2 * Number times for an associative array.

S Number Values

A struct literal. Value is repeated Number times.

Name:
Namestart
Namestart Namechars

Namestart:
_
Alpha

Namechar:
Namestart
Digit

Namechars:
Namechar
Namechar Namechars

A Name is a standard D identifier.

LName:
Number Name
Number __S Number    // function-local parent symbols

Number:
Digit
Digit Number

Digit:
0
1
2
3
4
5
6
7
8
9

An LName is a name preceded by a Number giving the number of characters in the Name.

Back references

Any LName or non-basic Type (i.e. any type that does not encode as a fixed one or two character sequence) that has been emitted to the mangled symbol before will not be emitted again, but is referenced by a special sequence encoding the relative position of the original occurrence in the mangled symbol name.

Numbers in back references are encoded with base 26 by upper case letters A - Z for higher digits but lower case letters a - z for the last digit.

TypeBackRef:
Q NumberBackRef

IdentifierBackRef:
Q NumberBackRef

NumberBackRef:
lower-case-letter
upper-case-letter NumberBackRef

To distinguish between the type of the back reference a look-up of the back referenced character is necessary: An identifier back reference always points to a digit 0 to 9, while a type back reference always points to a letter.

Type Mangling

Types are mangled using a simple linear scheme:

Type:
TypeModifiersopt TypeX
TypeBackRef

TypeX:
TypeArray
TypeStaticArray
TypeAssocArray
TypePointer
TypeFunction
TypeIdent
TypeClass
TypeStruct
TypeEnum
TypeTypedef
TypeDelegate
TypeVoid
TypeByte
TypeUbyte
TypeShort
TypeUshort
TypeInt
TypeUint
TypeLong
TypeUlong
TypeCent
TypeUcent
TypeFloat
TypeDouble
TypeReal
TypeIfloat
TypeIdouble
TypeIreal
TypeCfloat
TypeCdouble
TypeCreal
TypeBool
TypeChar
TypeWchar
TypeDchar
TypeNoreturn
TypeNull
TypeTuple
TypeVector

TypeModifiers:
Const
Wild
Wild Const
Shared
Shared Const
Shared Wild
Shared Wild Const
Immutable

Shared:
O

Const:
x

Immutable:
y

Wild:
Ng

TypeArray:
A Type

TypeStaticArray:
G Number Type

TypeAssocArray:
H Type Type

TypePointer:
P Type

TypeVector:
Nh Type

TypeFunction:
TypeFunctionNoReturn Type

TypeFunctionNoReturn:
CallConvention FuncAttrsopt Parametersopt ParamClose

CallConvention:
F       // D
U       // C
W       // Windows
R       // C++
Y       // Objective-C

FuncAttrs:
FuncAttr
FuncAttr FuncAttrs

FuncAttr:
FuncAttrPure
FuncAttrNothrow
FuncAttrRef
FuncAttrProperty
FuncAttrNogc
FuncAttrReturn
FuncAttrScope
FuncAttrTrusted
FuncAttrSafe
FuncAttrLive

Function attributes are emitted in the order as listed above, with the exception of return and scope. return comes before scope when this is a return scope parameter, and after scope when this is a scope and return ref parameter.

FuncAttrPure:
Na

FuncAttrNogc:
Ni

FuncAttrNothrow:
Nb

FuncAttrProperty:
Nd

FuncAttrRef:
Nc

FuncAttrReturn:
Nj

FuncAttrScope:
Nl

FuncAttrTrusted:
Ne

FuncAttrSafe:
Nf

FuncAttrLive:
Nm

Parameters:
Parameter
Parameter Parameters

Parameter:
Parameter2
M Parameter2     // scope
Nk Parameter2    // return

Parameter2:
Type
I Type     // in
J Type     // out
K Type     // ref
L Type     // lazy

ParamClose:
X     // variadic T t...) style
Y     // variadic T t,...) style
Z     // not variadic

TypeIdent:
I QualifiedName

TypeClass:
C QualifiedName

TypeStruct:
S QualifiedName

TypeEnum:
E QualifiedName

TypeTypedef:
T QualifiedName

TypeDelegate:
D TypeModifiersopt TypeFunction

TypeVoid:
v

TypeByte:
g

TypeUbyte:
h

TypeShort:
s

TypeUshort:
t

TypeInt:
i

TypeUint:
k

TypeLong:
l

TypeUlong:
m

TypeCent:
zi

TypeUcent:
zk

TypeFloat:
f

TypeDouble:
d

TypeReal:
e

TypeIfloat:
o

TypeIdouble:
p

TypeIreal:
j

TypeCfloat:
q

TypeCdouble:
r

TypeCreal:
c

TypeBool:
b

TypeChar:
a

TypeWchar:
u

TypeDchar:
w

TypeNoreturn:
Nn

TypeNull:
n

TypeTuple:
B Parameters Z

Function Calling Conventions

The extern (C) and extern (D) calling convention matches the C calling convention used by the supported C compiler on the host system. Except that the extern (D) calling convention for Windows x86 is described here.

Register Conventions

- EAX, ECX, EDX are scratch registers and can be destroyed by a function.

- EBX, ESI, EDI, EBP must be preserved across function calls.

- EFLAGS is assumed destroyed across function calls, except for the direction flag which must be forward.

- The FPU stack must be empty when calling a function.

- The FPU control word must be preserved across function calls.

- Floating point return values are returned on the FPU stack. These must be cleaned off by the caller, even if they are not used.

Return Value

- The types bool, byte, ubyte, short, ushort, int, uint, pointer, Object, and interfaces are returned in EAX.

- long and ulong are returned in EDX,EAX, where EDX gets the most significant half.

- float, double, real, ifloat, idouble, ireal are returned in ST0.

- cfloat, cdouble, creal are returned in ST1,ST0 where ST1 is the real part and ST0 is the imaginary part.

- Dynamic arrays are returned with the pointer in EDX and the length in EAX.

- Associative arrays are returned in EAX.

- References are returned as pointers in EAX.

- Delegates are returned with the pointer to the function in EDX and the context pointer in EAX.

- 1, 2 and 4 byte structs and static arrays are returned in EAX.

- 8 byte structs and static arrays are returned in EDX,EAX, where EDX gets the most significant half.

- For other sized structs and static arrays, the return value is stored through a hidden pointer passed as an argument to the function.

- Constructors return the this pointer in EAX.

Parameters

The parameters to the non-variadic function:

foo(a1, a2, ..., an);

are passed as follows:

a1

a2

...

an

hidden

this

where hidden is present if needed to return a struct value, and this is present if needed as the this pointer for a member function or the context pointer for a nested function.

The last parameter is passed in EAX rather than being pushed on the stack if the following conditions are met:

- It fits in EAX.

- It is not a 3 byte struct.

- It is not a floating point type.

Parameters are always pushed as multiples of 4 bytes, rounding upwards, so the stack is always aligned on 4 byte boundaries. They are pushed most significant first. out and ref are passed as pointers. Static arrays are passed as pointers to their first element. On Windows, a real is pushed as a 10 byte quantity, a creal is pushed as a 20 byte quantity. On Linux, a real is pushed as a 12 byte quantity, a creal is pushed as two 12 byte quantities. The extra two bytes of pad occupy the ‘most significant’ position.

The callee cleans the stack.

The parameters to the variadic function:

void foo(int p1, int p2, int[] p3...)
foo(a1, a2, ..., an);

are passed as follows:

p1

p2

a3

hidden

this

The variadic part is converted to a dynamic array and the rest is the same as for non-variadic functions.

The parameters to the variadic function:

void foo(int p1, int p2, ...)
foo(a1, a2, a3, ..., an);

are passed as follows:

an

...

a3

a2

a1

_arguments

hidden

this

The caller is expected to clean the stack. _argptr is not passed, it is computed by the callee.

Exception Handling

Windows 32 bit

Conforms to the Microsoft Windows Structured Exception Handling conventions.

Linux, FreeBSD and OS X

Conforms to the DWARF (debugging with attributed record formats) Exception Handling conventions.

Windows 64 bit

Uses static address range/handler tables. It is not compatible with the MSVC x64 exception handling tables. The stack is walked assuming it uses the EBP/RBP stack frame convention. The EBP/RBP convention must be used for every function that has an associated EH (Exception Handler) table.

For each function that has exception handlers, an EH table entry is generated.

EH Table Entry

field

description

void*

pointer to start of function

DHandlerTable*

pointer to corresponding EH data

uint

size in bytes of the function

The EH table entries are placed into the following special segments, which are concatenated by the linker.

EH Table Segment

Operating System

Segment Name

Win32

FI

Win64

._deh$B

Linux

.deh_eh

FreeBSD

.deh_eh

OS X

__deh_eh, __DATA

The rest of the EH data can be placed anywhere, it is immutable.

DHandlerTable

field

description

void*

pointer to start of function

uint

offset of ESP/RSP from EBP/RBP

uint

offset from start of function to return code

uint

number of entries in DHandlerInfo[]

DHandlerInfo[]

array of handler information

DHandlerInfo

field

description

uint

offset from function address to start of guarded section

uint

offset of end of guarded section

int

previous table index

uint

if != 0 offset to DCatchInfo data from start of table

void*

if not null, pointer to finally code to execute

DCatchInfo

field

description

uint

number of entries in DCatchBlock[]

DCatchBlock[]

array of catch information

DCatchBlock

field

description

ClassInfo

catch type

uint

offset from EBP/RBP to catch variable

void*, catch handler code

Garbage Collection

The interface to this is found in Druntime's core/gc/gcinterface.d.

ModuleInfo Instance

An instance of ModuleInfo is generated by the compiler and inserted into the object file for every module. ModuleInfo contains information about the module that is useful to the D runtime library:

- If the module has a static constructor, static destructor, shared static constructor, or shared static destructor.

- A reference to any unit tests defined by the module.

- An array of references to any imported modules that have one or more of:

1. static constructors

2. static destructors

3. shared static constructors

4. shared static destructors

5. unit tests

6. transitive imports of any module that contains one or more of 1..5

7. order independent constructors (currently needed for implementing -cov)

This enables the runtime to run the unit tests, the module constructors in a depth-first order, and the module destructors in the reverse order.

- An array of references to ClassInfo for each class defined in the module.
Note: this feature may be removed.

ModuleInfo is defined in Druntime's object.d, which must match the compiler's output in both the values of flags and layout of fields.

Modules compiled with -betterC do not have a ModuleInfo instance generated, because such modules must work without the D runtime library. Similarly, ImportC modules do not generate a ModuleInfo.

Module Initialization and Termination

All the static constructors for a module are aggregated into a single function, and a pointer to that function is inserted into the ctor member of the ModuleInfo instance for that module.

All the static destructors for a module are aggregated into a single function, and a pointer to that function is inserted into the dtor member of the ModuleInfo instance for that module.

Unit Testing

All the unit tests for a module are aggregated into a single function, and a pointer to that function is inserted into the unitTest member of the ModuleInfo instance for that module.

Runtime Helper Functions

These are found in Druntime's rt/.

Symbolic Debugging

D has types that are not represented in existing C or C++ debuggers. These are dynamic arrays, associative arrays, and delegates. Representing these types as structs causes problems because function calling conventions for structs are often different than that for these types, which causes C/C++ debuggers to misrepresent things. For these debuggers, they are represented as a C type which does match the calling conventions for the type.

Types for C Debuggers

D type

C representation

dynamic array

unsigned long long

associative array

void*

delegate

long long

dchar

unsigned long

For debuggers that can be modified to accept new types, the following extensions help them fully support the types.

Codeview Debugger Extensions

The D dchar type is represented by the special primitive type 0x78.

D makes use of the Codeview OEM generic type record indicated by LF_OEM (0x0015). The format is:

Codeview OEM Extensions for D

field size

2

2

2

2

2

2

D Type

Leaf Index

OEM Identifier

recOEM

num indices

type index

type index

dynamic array

LF_OEM

OEM

1

2

@index

@element

associative array

LF_OEM

OEM

2

2

@key

@element

delegate

LF_OEM

OEM

3

2

@this

@function

where:

OEM

0x42

index

type index of array index

key

type index of key

element

type index of array element

this

type index of context pointer

function

type index of function

These extensions can be pretty-printed by obj2asm. The Ddbg debugger supports them.

Vector Extensions

CPUs often support specialized vector types and vector operations (a.k.a. media instructions). Vector types are a fixed array of floating or integer types, and vector operations operate simultaneously on them.

Specialized Vector types provide access to them.

The VectorBaseType must be a Static Array. The VectorElementType is the unqualified element type of the static array. The dimension of the static array is the number of elements in the vector.

Implementation Defined: Which vector types are supported depends on the target. The implementation is expected to only support the vector types and operations that are implemented in the target's hardware.

Rationale: Emulating unsupported vector types and operations can exhibit such poor performance that the user is likely better off selecting a different algorithm than relying on emulation.

Best Practices: Use the declarations in core.simd instead of the language Vector grammar.

core.simd

Vector types and operations are introduced by importing core.simd:

import core.simd;

Implementation Defined: These types and operations will be the ones defined for the architecture the compiler is targeting. If a particular CPU family has varying support for vector types, an additional runtime check may be necessary. The compiler does not emit runtime checks; those must be done by the programmer.

Implementation Defined: Depending on the target architecture, compiler flags may be required to activate support for SIMD types.

The types defined will all follow the naming convention:

typeNN

where type is the vector element type and NN is the number of those elements in the vector type. The type names will not be keywords.
Properties

Vector types have the property:

Vector Type Properties

Property

Description

.array

Returns static array representation

Vectors support the following properties based on the vector element type. The value produced is that of a vector of the same type with each element set to the value corresponding to the property value for the element type.

Integral Vector Type Properties

Property

Description

.min

minimum value

.max

maximum value

Floating Point Vector Type Properties

Property

Description

.epsilon

smallest increment to the value 1

.infinity

infinity value

.max

largest representable value that is not infinity

.min_normal

smallest representable value that is not 0

.nan

NaN value

Conversions

Vector types of the same size (number_of_elements * size_of_element) can be implicitly converted among each other, this is done as a reinterpret cast (a type paint). Vector types can be cast to their VectorBaseType.

Integers and floating point values can be implicitly converted to their vector equivalents:

int4 v = 7;
v = 3 + v;   // add 3 to each element in v

Accessing Individual Vector Elements

They cannot be accessed directly, but can be when converted to an array type:

int4 v;
(cast(int*)&v)[3] = 2;   // set 3rd element of the 4 int vector
(cast(int[4])v)[3] = 2;  // set 3rd element of the 4 int vector
v.array[3] = 2;          // set 3rd element of the 4 int vector
v.ptr[3] = 2;            // set 3rd element of the 4 int vector

Conditional Compilation

If vector extensions are implemented, the version identifier D_SIMD is set.

Whether a type exists or not can be tested at compile time with an IsExpression:

static if (is(typeNN))
... yes, it is supported ...
else
... nope, use workaround ...

Whether a particular operation on a type is supported can be tested at compile time with:

float4 a,b;
static if (__traits(compiles, a+b))
... yes, add is supported for float4 ...
else
... nope, use workaround ...

For runtime testing to see if certain vector instructions are available, see the functions in core.cpuid.

A typical workaround for unsupported vector operations would be to use array operations instead:

float4 a,b;
static if (__traits(compiles, a/b))
c = a / b;
else
c[] = a[] / b[];

X86 And X86_64 Vector Extension Implementation

Implementation Defined:

The following describes the specific implementation of the vector types for the X86 and X86_64 architectures.

The vector extensions are currently implemented for the OS X 32 bit target, and all 64 bit targets.

core.simd defines the following types:

Vector Types

Type Name

Description

gcc Equivalent

void16

16 bytes of untyped data

no equivalent

byte16

16 bytes

signed char __attribute__((vector_size(16)))

ubyte16

16 ubytes

unsigned char __attribute__((vector_size(16)))

short8

8 shorts

short __attribute__((vector_size(16)))

ushort8

8 ushorts

ushort __attribute__((vector_size(16)))

int4

4 ints

int __attribute__((vector_size(16)))

uint4

4 uints

unsigned __attribute__((vector_size(16)))

long2

2 longs

long __attribute__((vector_size(16)))

ulong2

2 ulongs

unsigned long __attribute__((vector_size(16)))

float4

4 floats

float __attribute__((vector_size(16)))

double2

2 doubles

double __attribute__((vector_size(16)))

void32

32 bytes of untyped data

no equivalent

byte32

32 bytes

signed char __attribute__((vector_size(32)))

ubyte32

32 ubytes

unsigned char __attribute__((vector_size(32)))

short16

16 shorts

short __attribute__((vector_size(32)))

ushort16

16 ushorts

ushort __attribute__((vector_size(32)))

int8

8 ints

int __attribute__((vector_size(32)))

uint8

8 uints

unsigned __attribute__((vector_size(32)))

long4

4 longs

long __attribute__((vector_size(32)))

ulong4

4 ulongs

unsigned long __attribute__((vector_size(32)))

float8

8 floats

float __attribute__((vector_size(32)))

double4

4 doubles

double __attribute__((vector_size(32)))

Note: for 32 bit gcc and clang, it's long long instead of long.

Supported 128-bit Vector Operators

Operator

void16

byte16

ubyte16

short8

ushort8

int4

uint4

long2

ulong2

float4

double2

=

×

×

×

×

×

×

×

×

×

×

×

+

–

×

×

×

×

×

×

×

×

×

×

-

–

×

×

×

×

×

×

×

×

×

×

*

–

–

–

×

×

–

–

–

–

×

×

/

–

–

–

–

–

–

–

–

–

×

×

&

–

×

×

×

×

×

×

×

×

–

–

|

–

×

×

×

×

×

×

×

×

–

–

^

–

×

×

×

×

×

×

×

×

–

–

+=

–

×

×

×

×

×

×

×

×

×

×

-=

–

×

×

×

×

×

×

×

×

×

×

*=

–

–

–

×

×

–

–

–

–

×

×

/=

–

–

–

–

–

–

–

–

–

×

×

&=

–

×

×

×

×

×

×

×

×

–

–

|=

–

×

×

×

×

×

×

×

×

–

–

^=

–

×

×

×

×

×

×

×

×

–

–

==

–

×

×

×

×

×

×

×

×

×

×

!=

–

×

×

×

×

×

×

×

×

×

×

<

–

×

×

×

×

×

×

×

×

×

×

<=

–

×

×

×

×

×

×

×

×

×

×

>=

–

×

×

×

×

×

×

×

×

×

×

>

–

×

×

×

×

×

×

×

×

×

×

unary~

–

×

×

×

×

×

×

×

×

–

–

unary+

–

×

×

×

×

×

×

×

×

×

×

unary-

–

×

×

×

×

×

×

×

×

×

×

Supported 256-bit Vector Operators

Operator

void32

byte32

ubyte32

short16

ushort16

int8

uint8

long4

ulong4

float8

double4

=

×

×

×

×

×

×

×

×

×

×

×

+

–

×

×

×

×

×

×

×

×

×

×

-

–

×

×

×

×

×

×

×

×

×

×

*

–

–

–

–

–

–

–

–

–

×

×

/

–

–

–

–

–

–

–

–

–

×

×

&

–

×

×

×

×

×

×

×

×

–

–

|

–

×

×

×

×

×

×

×

×

–

–

^

–

×

×

×

×

×

×

×

×

–

–

+=

–

×

×

×

×

×

×

×

×

×

×

-=

–

×

×

×

×

×

×

×

×

×

×

*=

–

–

–

–

–

–

–

–

–

×

×

/=

–

–

–

–

–

–

–

–

–

×

×

&=

–

×

×

×

×

×

×

×

×

–

–

|=

–

×

×

×

×

×

×

×

×

–

–

^=

–

×

×

×

×

×

×

×

×

–

–

==

–

×

×

×

×

×

×

×

×

×

×

!=

–

×

×

×

×

×

×

×

×

×

×

<

–

×

×

×

×

×

×

×

×

×

×

<=

–

×

×

×

×

×

×

×

×

×

×

>=

–

×

×

×

×

×

×

×

×

×

×

>

–

×

×

×

×

×

×

×

×

×

×

unary~

–

×

×

×

×

×

×

×

×

–

–

unary+

–

×

×

×

×

×

×

×

×

×

×

unary-

–

×

×

×

×

×

×

×

×

×

×

Operators not listed are not supported at all.

Vector Operation Intrinsics

See core.simd for the supported intrinsics.

Better C

BetterC is a subset of D that doesn't depend on the D runtime library, only the C runtime library.

Warning: While the D runtime library and standard library are not linked in, it is still possible to import several packages (core.* and std.*) and use some templates, types and function declarations without bodies. However, these libraries were not written for BetterC, so you will need to determine which symbols may be used. Expect cryptic compiler or linker errors.

Linking

It is straightforward to link C functions and libraries into D programs. But linking D functions and libraries into C programs is not straightforward.

D programs generally require:

1. The D runtime library to be linked in, because many features of the core language require runtime library support.

2. The main() function to be written in D, to ensure that the required runtime library support is properly initialized.

To link D functions and libraries into C programs, it's necessary to only require the C runtime library to be linked in. This is accomplished by defining a subset of D that fits this requirement, called BetterC.

Better C

Implementation Defined: BetterC is typically enabled by setting the -betterC command line flag for the implementation.

When BetterC is enabled, the predefined version D_BetterC can be used for conditional compilation.

An entire program can be written in BetterC by supplying a C main() function:

extern(C) void main()
{
import core.stdc.stdio : printf;
printf("Hello betterC\n");
}

> dmd -betterC hello.d && ./hello
Hello betterC

Limiting a program to this subset of runtime features is useful when targeting constrained environments where the use of such features is not practical or possible.

BetterC makes embedding D libraries in existing larger projects easier by:

1. Simplifying the process of integration at the build-system level

2. Removing the need to ensure that Druntime is properly initialized on calls to the library, for situations when an initialization step is not performed or would be difficult to insert before the library is used.

3. Mixing memory management strategies (GC + manual memory management) can be tricky, hence removing D's GC from the equation may be worthwhile sometimes.

Note: BetterC and ImportC are very different. ImportC is an actual C compiler. BetterC is a subset of D that relies only on the existence of the C Standard library.

Retained Features

Nearly the full language remains available. Highlights include:

1. Unrestricted use of compile-time features

2. Full metaprogramming facilities

3. Nested functions, nested structs, delegates and lambdas

4. Member functions, constructors, destructors, operating overloading, etc.

5. The full module system

6. Array slicing, and array bounds checking

7. RAII (yes, it can work without exceptions)

8. scope(exit)

9. Memory safety protections

10. Interfacing to C++

11. COM classes and C++ classes

12. assert failures are directed to the C runtime library

13. switch with strings

14. final switch

15. unittest

16. printf format validation

Running unittests in -betterC
While testing can be done without the -betterC flag, it is sometimes desirable to run the testsuite in -betterC too. unittest blocks can be listed with the getUnitTests trait:

unittest
{
assert(0);
}

extern(C) void main()
{
static foreach(u; __traits(getUnitTests, __traits(parent, main)))
u();
}

> dmd -betterC -unittest -run test.d
dmd_runpezoXK: foo.d:3: Assertion `0' failed.

However, in -betterC, assert expressions don't use Druntime's assert and are directed to assert from the C runtime library instead.
Unavailable Features

D features not available with BetterC:

1. Garbage Collection

2. TypeInfo and ModuleInfo

3. Classes

4. Built-in threading (e.g. core.thread)

5. Dynamic arrays (though slices of static arrays and pointers work)

6. Associative arrays

7. Exceptions

8. synchronized and core.sync

9. Static module constructors or destructors

ImportC

ImportC is a C compiler embedded into the D implementation. It enables direct importation of C files, without needing to manually prepare a D file corresponding to the declarations in the C file. It directly compiles C files into modules that can be linked in with D code to form an executable. It can be used as a C compiler to compile and link 100% C programs.

Note: ImportC and BetterC are very different. ImportC is an actual C compiler. BetterC is a subset of D that relies only on the existence of the C Standard library. BetterC code can be linked with ImportC code, too.

Quick Examples

C code in file hello.c:

#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}

Compile and run:

dmd hello.c
./hello
hello world

C function in file functions.c:

int square(int i)
{
return i * i;
}

D program in file demo.d:

import std.stdio;
import functions;
void main()
{
int i = 7;
writefln("The square of %s is %s", i, square(i));
}

Compile and run:

dmd demo.d functions.c
./demo
The square of 7 is 49

ImportC Dialect

There are many versions of C. ImportC is an implementation of ISO/IEC 9899:2011, which will be referred to as C11. References to the C11 Standard will be C11 followed by the paragraph number. Prior versions, such as C99, C89, and K+R C, are not supported.

Implementation Defined: Adjustment to the ImportC dialect is made to match the behavior of the C compiler that the D compiler is matched to, i.e. the Associated C Compiler.

Further adjustment is made to take advantage of some of the D implementation's capabilities.

Note: This is not a C reference manual nor programming tutorial. It describes the specifics of the dialect of C that ImportC is, and how to use it effectively.

Invoking ImportC

The ImportC compiler can be invoked:

- directly via the command line

- indirectly via importing a C file

ImportC Files on the Command Line

ImportC files have one of the extensions .i, .h, or .c.

dmd hello.c

will compile hello.c with ImportC and link it to create the executable file hello (hello.exe on Windows)

Best Practices: explicitly use a .i, .h, or .c extension when specifying C files on the command line.

Importing C Files from D Code

Use the D ImportDeclaration:

import hello;

which will, if hello is not a D file, and has an extension .i, .h, or .c, compile hello with ImportC.

Preprocessor

ImportC does not have a preprocessor. It is designed to compile C files after they have been first run through the C preprocessor. ImportC can automatically run the C preprocessor associated with the Associated C Compiler, or a preprocessor can be run manually.

Running the Preprocessor Automatically

If the C file has a .c extension, ImportC will run the preprocessor for it automatically.

1. When compiling for Windows with the -m32omf switch, sppn.exe will be used as the preprocessor.

2. When compiling for Windows with the -m32mscoff or the -m64 switch, cl.exe /P /Zc:preprocessor will be used as the preprocessor.

3. When compiling for OSX, the clang -E preprocessor will be used.

4. Otherwise the cpp preprocessor will be used.

The -v switch can be used to observe the command that invokes the preprocessor.

The -Ppreprocessorflag switch passes preprocessorflag to the preprocessor.

importc.h

The druntime file src/importc.h will automatically be #included first. importc.h provides adjustments to the source code to account for various C compiler extensions not supported by ImportC.

no-builtin-macro-redefined

On Posix systems, ImportC will pass the switch -Wno-builtin-macro-redefined to the C preprocessor used by gcc and clang. This switch does not exist in gcc preprocessors made before 2008. The workaround is to run the preprocessor manually.

Running the Preprocessor Manually

If the C file has a .i extension, the file is presumed to be already preprocessed. Preprocessing can be run manually:

Digital Mars C Preprocessor sppn.exe

sppn.exe runs on Win32 and is invoked as:

sppn file.c

and the preprocessed output is written to file.i.

Gnu C Preprocessor

The Gnu C Preprocessor can be invoked as:

gcc -E file.c > file.i

Clang C Preprocessor

The Clang Preprocessor can be invoked as:

clang -E file.c -o file.i

Microsoft VC Preprocessor

The VC Preprocessor can be invoked as:

cl /P /Zc:preprocessor file.c -Fifile.i

and the preprocessed output is written to file.i.

dmpp C Preprocessor

The dmpp C Preprocessor can be invoked as:

dmpp file.c

and the preprocessed output is written to file.i.

Preprocessor Macros

ImportC collects all the #define macros from the preprocessor run when it is run automatically. Some can be made available to D code by interpreting them as declarations. The variety of macros that can be interpreted as D declarations may be expanded, but will never encompass all the metaprogramming uses of C macros.

Manifest Constants

Macros that look like manifest constants, such as:

#define COLOR 0x123456
#define HELLO "hello"

are interpreted as D manifest constant declarations of the form:

enum COLOR = 0x123456;
enum HELLO = "hello";

Function-Like Macros

Many macros look like functions, and can be treated as template functions:

#define ABC a + b
#define DEF(a) (a + x)

auto ABC() { return a + b; }
auto DEF(T)(T a) { return a + x; }

Some macro formulations, however, will not produce the same result:

#define ADD(a, b) a + b
int x = ADD(1, 2) * 4; // sets x to 9

auto ADD(U, V)(U a, V b) { return a + b; }
int x = ADD(1, 2) * 4; // sets x to 12

Best Practices: Always use parentheses around arguments and entire expressions:

#define ADD(a, b) ((a) + (b))

Another area of trouble is side effects in the arguments:

#define DOUBLE(x) ((x) + (x))

int i = 0;
DOUBLE(i++);
assert(i == 2);  // D result will be 1, C result will be 2

and treating arguments as references:

#define INC(x) (++x)

int i = 0;
INC(i);
assert(i == 1); // D result will be 0, C result will be 1

Predefined Macros

ImportC does not predefine any macros.

To distinguish an ImportC compile vs some other C compiler, use:

#if __IMPORTC__

__IMPORTC__ is defined in src/importc.h which is automatically included when the preprocessor is run. importc.h contains many macro definitions that are used to adapt various C source code vagaries to ImportC.

Preprocessor Directives

ImportC supports these preprocessor directives:

Line control

C11 6.10.4

Linemarker

linemarker directives are normally embedded in the output of C preprocessors.

Pragmas

The following pragmas are supported:

- #pragma pack ( )

- #pragma pack ( show )

- #pragma pack ( push )

- #pragma pack ( push , identifier )

- #pragma pack ( push , integer )

- #pragma pack ( push , identifier , integer )

- #pragma pack ( pop )

- #pragma pack ( pop PopList )

#pragma attribute
The following pragma for ImportC allows to set default storage classes for function declarations:

#pragma attribute(push, [storage classes...])

The storage classes nothrow, nogc and pure are supported. Unrecognized attributes are ignored. Enabling a default storage class affects all function declarations after the pragma until it is disabled with another pragma. Declarations in includes are also affected. The following example enables @nogc and nothrow for a library:

#pragma attribute(push, nogc, nothrow)
#include <somelibrary.h>

The changed storage classes are pushed on a stack. The last change can be undone with the following pragma:

#pragma attribute(pop)

This can also disable multiple default storage classes at the same time, if they were enabled with a single #pragma attribute(push, ...) directive.
src/__builtins.di

The first thing the compiler does when preprocessing is complete is to import src/__builtins.di. It provides support for various builtins provided by other C compilers. __builtins.di is a D file.

Implementation

The implementation defined characteristics of ImportC are:

Enums

enumeration-constants are always typed as integers.

The expression that defines the value of an enumeration-constant must be an integer. if an underlying int is specified in the enumeration, the defining expression must evaluate to a value that fits in an int. if no underlying int is specfied, the compiler chooses a large enough integer type to accomodate the size of the expression.

enum E { a = -10, b = 0x81231234 }; // ok
enum F { c =  0x812312345678 }; // ok, c expands to 8 byte to accomodate expression
enum G : int { c = 0x80000000 }; // error, doesn't fit in int
enum G { d = 1.0 };             // error, not integral type

Bit Fields

There are many implementation defined aspects of C11 bit fields. ImportC's behavior adjusts to match the behavior of the associated C compiler on the target platform.

Implicit Function Declarations

Implicit function declarations:

int main()
{
func();  // implicit declaration of func()
}

were allowed in K+R C and C89, but were invalidated in C99 and C11. Although many C compilers still support them, ImportC does not.

Rationale: Implicit function declarations are very error-prone and cause hard to find bugs.

#pragma STDC FENV_ACCESS

This is described in C11 7.6.1

#pragma STDC FENV_ACCESS on-off-switch

on-off-switch:
ON
OFF
DEFAULT

It is completely ignored.

Limitations

Exception Handling

ImportC is assumed to never throw exceptions. setjmp and longjmp are not supported.

Const

C11 specifies that const only applies locally. const in ImportC applies transitively, meaning that although:

int *const p;

means in C11 that p is a const pointer to int, in ImportC it means p is a const pointer to a const int.

Volatile

The volatile type-qualifier (C11 6.7.3) is ignored. Use of volatile to implement shared memory access is unlikely to work anyway, _Atomic is for that. To use volatile as a device register, call a function to do it that is compiled separately, or use inline assembler.

Restrict

The restrict type-qualifier (C11 6.7.3) is ignored.

_Atomic

The _Atomic type-qualifier (C11 6.7.3) is ignored. To do atomic operations, use an externally compiled function for that, or the inline assembler.

Compatible Types

Compatible Types (C11 6.7.2) are identical types in ImportC.

Impedance Mismatch

While every effort is made to match up C and D so it "just works", the languages have some fundamental differences that appear now and then.

Keyword Mismatch

D and C use mostly the same keywords, C has keywords that D doesn't have, and vice versa. This does not affect compilation of C code, but it can cause difficulty when accessing C variables and types from D. For example, the D version keyword is not uncommonly used as a struct member in C:

C code in file defs.c:

struct S { int version; };

Accessing it from D:

import defs;
int tech(S* s) {
return s.version; // fails because version is a D keyword
}

A workaround is available:

import defs;
int tech(S* s) {
return __traits(getMember, *s, "version");
}

Same only Different Types

On some platforms, C long and unsigned long are the same size as int and unsigned int, respectively. On other platforms, C long and unsigned long are the same size as long long and unsigned long long. long double and long double _Complex can be the same size as double and double _Complex. In ImportC, these types that are the same size and signed-ness are treated as the same types.

_Generic

Generic selection expressions (C11 6.5.1.1) differ from ImportC. The types in Same only Different Types are indistinguishable in the type-name parts of generic-association. Instead of giving an error for duplicate types per C11 6.5.1.1-2, ImportC will select the first compatible type-name in the generic-assoc-list.

Extensions

Asm statement

For the D language, asm is a standard keyword, and its construct is shared with ImportC. For the C language, asm is an extension (J.5.10), and the recommendation is to instead use __asm__. All alternative keywords for asm are translated by the druntime file src/importc.h during the preprocessing stage.

The asm keyword may be used to embed assembler instructions, its syntax is implementation defined. The Digital Mars D compiler only supports the dialect of inline assembly as described in the documentation of the D x86 Inline Assembler.

asm in a function or variable declaration may be used to specify the mangle name for a symbol. Its use is analogous to pragma mangle.

char **myenviron asm("environ") = 0;

int myprintf(char *, ...) asm("printf");

Using asm to associate registers with variables is ignored.

Forward References

Any declarations in scope can be accessed, not just declarations that lexically precede a reference.

Ta *p;  // Ta is forward referenced
struct Sa { int x; };
typedef struct Sa Ta; // Ta is defined

struct S s;
int* p = &s.t.x;  // struct S definition is forward referenced
struct S { int a; struct T t; }; // T still forward referenced
struct T { int b; int x; }; // definition of struct T

C++ Style Tag Symbols

In C++, struct, union or enum tag symbols can be accessed without needing to be prefixed with the struct, union or enum keywords, as long as there is no other declaration with the same name at the same scope. ImportC behaves the same way.

For example, the following code is accepted by both C++ and ImportC:

struct s { int a; };

void g(int s)
{
struct s* p = (struct s*)malloc(sizeof(struct s));
p->a = s;
}

Whereas this is rejected by both C++ and ImportC, for the same reason.

struct s { int a; };

void g(int s)
{
s* p = (s*)malloc(sizeof(s));
p->a = s;
}

Compile Time Function Execution

Evaluating constant expressions includes executing functions in the same manner as D's CTFE can. A constant-expression invokes CTFE.

Examples:

_Static_assert("\x1"[0] == 1, "failed");

int mint1() { return -1; }
_Static_assert(mint1() == -1, "failed");

const int a = 7;
int b = a; // sets b to 7

Function Inlining

Functions for which the function body is present can be inlined by ImportC as well as by the D code that calls them.

Enum Base Types

Enums are extended with an optional EnumBaseType:

EnumDeclaration:
enum Identifier : EnumBaseType EnumBody

EnumBaseType:
Type

which, when supplied, causes the enum members to be implicitly cast to the EnumBaseType.

enum S : byte { A };
_Static_assert(sizeof(A) == 1, "A should be size 1");

Register Storage Class

Objects with register storage class are treated as auto declarations.

Objects with register storage class may have their address taken. C11 6.3.2.1-2

Arrays can have register storage class, and may be enregistered by the compiler. C11 6.3.2.1-3

typeof Operator

The typeof operator may be used as a type specifier:

type-specifier:
typeof-specifier

typeof-specifier:
typeof ( expression )
typeof ( type-name )

Import Declarations

Modules can be imported with a CImportDeclaration:

CImportDeclaration:
__import ImportList ;

Imports enable ImportC code to directly access D declarations and functions without the necessity of creating a .h file representing those declarations. The tedium and brittleness of keeping the .h file up-to-date with the D declarations is eliminated. D functions are available to be inlined.

Imports also enable ImportC code to directly import other C files without needing to create a .h file for them, either. Imported C functions become available to be inlined.

The ImportList works the same as it does for D.

The ordering of CImportDeclarations has no significance.

An ImportC file can be imported, the name of the C file to be imported is derived from the module name.

All the global symbols in the ImportC file become available to the importing module.

If a name is referred to in the importing file is not found, the global symbols in each imported file are searched for the name. If it is found in exactly one module, that becomes the resolution of the name. If it is found in multiple modules, it is an error.

Note: Since ImportC has no scope resolution operator, only global symbols can be found, and a qualification cannot be added to specifiy which module a symbols is in.

Preprocessor symbols in the imported module are not available to the importing module, and preprocessing symbols in the importing file are not available to the imported module.

A D module can be imported, in the same manner as that of a ImportDeclaration.

Imports can be circular.

__import core.stdc.stdarg; // get D declaration of va_list
__import mycode;           // import mycode.c

int foo()
{
va_list x;    // picks up va_list from core.stdc.stdarg
return 1 + A; // returns 4
}

mycode.c looks like:

enum E { A = 3; }

Best Practices: Avoid using preprocessor #defines like #define A 3. Use the enum form shown in the above example. Prefer const declarations over #defines. Rewrite function-style preprocessor macros as inline functions.

Control Z is End Of File

A control-Z character \x1A in the source text means End Of File.

Signed Integer Literal Larger Than long long

A signed integer constant with no suffix that is larger than a long long type, but will fit into an unsigned long long type, is accepted and typed as unsigned long long. This matches D behavior, and that of some C compilers.

Dot and Arrow Operators

The . operator is used to designate a member of a struct or union value. The -> operator is used to designate a member of a struct or union value pointed to by a pointer. The extension is that . and -> can be used interchangeably on values and pointers. This matches D's behavior for ..

Gnu and Clang Extensions

gcc and clang are presumed to have the same behavior w.r.t. extensions, so gcc as used here refers to both.

__attribute__ Extensions

The following __attribute__ extensions:

1. __attribute__((aligned(N)))

2. __attribute__((always_inline))

3. __attribute__((deprecated))

4. __attribute__((dllexport))

5. __attribute__((dllimport))

6. __attribute__((naked))

7. __attribute__((noinline))

8. __attribute__((noreturn))

9. __attribute__((nothrow))

10. __attribute__((pure))

11. __attribute__((vector_size(N)))

12. others are ignored

__attribute__((noreturn))

__attribute__((noreturn)) marks a function as never returning. gcc set this as an attribute of the function, it is not part of the function's type. In D, a function that never returns has the return type noreturn. The difference can be seen with the code:

attribute((noreturn)) int foo();
size_t x = sizeof(foo());

This code is accepted by gcc, but makes no sense for D. Hence, although it works in ImportC, it is not representable as D code, meaning one must use judgement in creating a .di file to interface with C noreturn functions.

Furthermore, the D compiler takes advantage of noreturn functions by issuing compile time errors for unreachable code. Such unreachable code, however, is valid C11, and the ImportC compiler will accept it.

Best Practices: C code that uses the noreturn attribute should at the very least set the return type to void.

Visual C Extensions

All the Digital Mars C Extensions.

__stdcall Function Calling Convention

__stdcall sets the calling convention for a function to the Windows API calling convention.

int __stdcall foo(int x);

__declspec Attribute Extensions

The following __declspec extensions:

1. __declspec()

2. __declspec(align(N))

3. __declspec(deprecated)

4. __declspec(dllexport)

5. __declspec(dllimport)

6. __declspec(naked)

7. __declspec(noinline)

8. __declspec(noreturn)

9. __declspec(nothrow)

10. __declspec(thread)

11. others are ignored

__pragma Attribute Extensions

The following __pragma extensions:

1. __pragma(pack(N))

2. others are ignored

Digital Mars C Extensions

__stdcall Function Calling Convention

__declspec Attribute Extensions

The following __declspec extensions:

1. __declspec(dllexport)

2. __declspec(dllimport)

3. __declspec(naked)

4. __declspec(thread)

ImportC from D's Point of View

There is no one-to-one mapping of C constructs to D constructs, although it is very close. What follows is a description of how the D side views the C declarations that are imported.

Module Name

The module name assigned to the ImportC file is the filename stripped of its path and extension. This is just like the default module name assigned to a D module that does not have a module declaration.

extern (C)

All C symbols are extern (C).

Enums

The C enum:

enum E { A, B = 2 };

appears to D code as:

enum E : int { A, B = 2 }
alias A = E.A;
alias B = E.B;

The .min and .max properties are available:

static assert(E.min == 0 && E.max == 2);

Tag Symbols

Tag symbols are the identifiers that appear after the struct, union, and enum keywords, (C11 6.7.2.3). In C, they are placed in a different symbol table from other identifiers. This means two different symbols can use the same name:

int S;
struct S { int a, b; };
S = 3;
struct S *ps;

D does not make this distinction. Given a tag symbol that is the only declaration of an identifier, that's what the D compiler recognizes. Given a tag symbol and a non-tag symbol that share an identifier, the D compiler recognizes the non-tag symbol. This is normally not a problem due to the common C practice of applying typedef, as in:

typedef struct S { int a, b; } S;

The D compiler recognizes the typedef applied to S, and the code compiles as expected. But when typedef is absent, as in:

int S;
struct S { int a, b; };

The most pragmatic workaround is to add a typedef to the C code:

int S;
struct S { int a, b; };
typedef struct S S_t;    // add this typedef

Then the D compiler can access the struct tag symbol via S_t.

Wrapping C Code

Many difficulties with adapting C code to ImportC can be done without editing the C code itself. Wrap the C code in another C file and then #include it. Consider the following problematic C file file.c:

void func(int *__restrict p);
int S;
struct S { int a, b; };

The problems are that __restrict is not a type qualifier recognized by ImportC (or C11), and the struct S is hidden from D by the declaration int S;. To wrap file.c with a fix, create the file file_ic.c with the contents:

#define __restrict restrict
#include "file.c"
typedef struct S S_t;

Then, import file_ic; instead of import file;, and use S_t when struct S is desired.

Converting C Code to D Code

Sometimes its desirable to go further than importing C code, to actually do a C source to D source conversion. Reasons include:

- Migrating a C project to a D project.

- Equivalent D code can compile much faster, due to not needing a preprocessor, etc.

- Tweaking the D code to add attributes for memory safety, purity, etc.

- Eliminating the need for C-isms in the D part of the project.

This can be done with the D compiler by using the -Hf switch:

dmd -c mycode.c -Hf=mycode.di

which will convert the C source code in mycode.c to D source code in mycode.di. If the -inline switch is also used, it will emit the C function bodies as well, instead of just the function prototypes.

Impedance Mismatch

A precise mapping of C semantics, with all its oddities, to D source code is not always practical. ImportC uses C semantics in its semantic analysis to get much closer to exact C semantics than is expressible in D source code. Hence, the translation to D source code will be less than perfect. For example:

int S;
struct S { int a, b; };
int foo(struct S s)
{
return S + s.a;
}

will work fine in ImportC, because the int S and the struct S are in different symbol tables. But in the generated D code, both symbols would be in the same symbol table, and will collide. Such D source code translated from C will need to be adjusted by the user.

Nevertheless, reports from the field are that this conversion capability is a huge timesaver for users who need to deal with existing C code.

Warnings

Many suspicious C constructs normally cause warnings to be emitted by default by typical compilers, such as:

int *p = 3; // Warning: integer implicitly converted to pointer

ImportC does not emit warnings. The presumption is the user will be importing existing C code developed using another C compiler, and it is written as intended. If C11 says it is legal, ImportC accepts it.

ImportC++

ImportC will not compile C++ code. For that, use dpp.

Other Solutions

dpp by Atila Neves

dpp code

dpp Article

From the Article:

dpp is a compiler wrapper that will parse a D source file with the .dpp extension and expand in place any #include directives it encounters, translating all of the C or C++ symbols to D, and then pass the result to a D compiler (DMD by default).

Like DStep, dpp relies on libclang.

DStep by Jacob Carlborg

DStep code

DStep Article

From the Article:

DStep is a tool for automatically generating D bindings for C and Objective-C libraries. This is implemented by processing C or Objective-C header files and outputting D modules. DStep uses the Clang compiler as a library (libclang) to process the header files.

htod by Walter Bright

htod converts a C .h file to a D source file, suitable for importing into D code. htod is built from the front end of the Digital Mars C and C++ compiler. It works just like a C or C++ compiler except that its output is source code for a D module rather than object code.

How ImportC Works

ImportC's implementation is based on the idea that D's semantics are very similar to C's. ImportC gets its own parser, which converts the C syntax into the same AST (Abstract Syntax Tree) that D uses. The lexer for ImportC is the same as for D, but with some modifications here and there, such as the keywords and integer literals being different. Where the semantics of C differ from D, there are adjustments in the semantic analysis code in the D compiler.

This co-opting of the D semantic implementation allows ImportC to be able to do things like handle forward references, CTFE (Compile Time Function Execution), and inlining of C functions into D code. Being able to handle forward references means it is not necessary to even write a .h file to be able to import C declarations into D. Being able to perform CTFE is very handy for testing that ImportC is working without needing to generate an executable. But, in general, the strong temptation to add D features to ImportC has been resisted.

The optimizer and code generator are, of course, the same as D uses.

Live Functions
Experimental, Subject to Change, use -preview=dip1021 to activate
Ownership

If a memory object has only one pointer to it, that pointer is the owner of the memory object. With the single owner, it becomes straightforward to manage the memory for the object. It also becomes trivial to synchronize access to that memory object among multiple threads, because it can only be accessed by the thread that controls that single pointer.

This can be generalized to a graph of memory objects interconnected by pointers, where only a single pointer connects to that graph from elsewhere. That single pointer becomes the owner of all the memory objects in that graph.

When the owner of the graph is no longer needed, then the graph of memory objects it points to is no longer needed and can be safely disposed of. If the owner itself is no longer in use (i.e. is no longer live) and the owned memory objects are not disposed of, an error can be diagnosed.

Hence, the following errors can be statically detected:

int* allocate();    // allocate a memory object
void release(int*); // deallocate a memory object

@live void test()
{
auto p = allocate();
}   // error: p is not disposed of

@live void test()
{
auto p = allocate();
release(p);
release(p); // error: p was already disposed of
}

@live void test()
{
int* p = void;
release(p); // error, p does not have a defined value
}

@live void test()
{
auto p = allocate();
p = allocate(); // error: p was not disposed of
release(p);
}

Functions with the @live attribute enable diagnosing these sorts of errors by tracking the status of owner pointers.

Borrowing

Tracking the ownership status of a pointer can be safely extended by adding the capability of temporarilly borrowing ownership of a pointer from the owner. The owner can no longer use the pointer as long as the borrower is still using the pointer value (i.e. it is live). Once the borrower is no longer live, the owner can resume using it. Only one borrower can be live at any point.

Multiple borrower pointers can simultaneously exist if all of them are pointers to read only (const or immutable) data, i.e. none of them can modify the memory object(s) pointed to.

This is collectively called an Ownership/Borrowing system. It can be stated as:

At any point in the program, for each memory object, there is exactly one live mutable pointer to it or all the live pointers to it are read-only.

@live attribute

Function declarations annotated with the @live attribute are checked for compliance with the Ownership/Borrowing rules. The checks are run after other semantic processing is complete. The checking does not influence code generation.

Whether a pointer is allocated memory using the GC or some other storage allocator is immaterial to OB, they are not distinguished and are handled identically.

Class references are assumed to be allocated using either the GC or are allocated on the stack as scope classes, and are not tracked.

If @live functions call non-@live functions, those called functions are expected to present an @live compatible interface, although it is not checked. if non-@live functions call @live functions, arguments passed are expected to follow @live conventions.

It will not detect attempts to dereference null pointers or possibly null pointers. This is unworkable because there is no current method of annotating a type as a non-null pointer.

Tracked Pointers

The only pointers that are tracked are those declared in the @live function as this, function parameters or local variables. Variables from other functions are not tracked, even @live ones, as the analysis of interactions with other functions depends entirely on that function signature, not its internals. Parameters that are const are not tracked.

Pointer States

Each tracked pointer is in one of the following states:

Undefined

The pointer is in an invalid state. Dereferencing such a pointer is an error.

Owner

The owner is the sole pointer to a memory object graph. An Owner pointer normally does not have a scope attribute. If a pointer with the scope attribute is initialized with an expression not derived from a tracked pointer, it is an Owner. If an Owner pointer is assigned to another Owner pointer, the former enters the Undefined state.

void consume(int* o); // o is owner

@live int* f(int* p) // p is owner
{
writeln(*p);
// transfer ownership to `consume`
consume(p);
// p is now undefined
//writeln(*p); // error

int* q = new int; // q is owner
writeln(*q);
p = q; // transfer ownership
// q is now undefined
//writeln(*q); // error
writeln(*p);
return p;
}

Borrowed

A Borrowed pointer is one that temporarily becomes the sole live pointer to a memory object graph. It enters that state via assignment from an owner pointer or another borrowed pointer, and stays in that state until its last use. A Borrowed pointer must be scope and must be a pointer to mutable. A mutable scope pointer function parameter is a Borrowed pointer.

void consume(int* o); // o is owner
void borrow(scope int* b); // b is borrowed

@live void g(scope int* p) // p is borrowed
{
//consume(p); // error, p is not owner
borrow(p);

// lend p to q
int* q = p; // q is inferred as scope
// <-- using p here would end q's lifetime
writeln(*q);
// lifetime of q ends before p is used
writeln(*p); // OK
}

Readonly

A Readonly pointer acquires its value from an Owner or Borrowed pointer. While the Readonly pointer is live, only Readonly pointers can be acquired from that Owner. A Readonly pointer must be scope and also must not be a pointer to mutable.

@live void h(scope int* p)
{
// acquire 2 read only pointers
const q = p;
const r = q;
// <-- borrowing or using p here would end q and r's lifetime

// both q and r are live
writeln(*q);
writeln(*r);
// using p ends all its read only pointer lifetimes
writeln(*p);
//writeln(*q); // error
}

Lifetimes

The lifetime of a Borrowed or Readonly pointer value starts when it is assigned a value from an Owner or another Borrowed pointer, and ends at the last read of that value.

This is also known as Non-Lexical Lifetimes.

Pointer State Transitions

A pointer changes its state when one of these operations is done to it:

- storage is allocated for it (such as a local variable on the stack), which places the pointer in the Undefined state

- initialization (treated as assignment)

- assignment - the source and target pointers change state based on what states they are in and their types and storage classes

- passed to an out function parameter (changes state after the function returns), treated the same as initialization

- passed by ref to a function parameter, treated as an assignment to a Borrow or a Readonly depending on the storage class and type of the parameter

- returned from a function

- it is passed by value to a function parameter, which is treated as an assignment to that parameter.

- it is implicitly passed by ref as a closure variable to a nested function

- the address of the pointer is taken, which is treated as assignment to whoever receives the address

- the address of any part of the memory object graph is taken, which is treated as assignment to whoever receives that address

- a pointer value is read from any part of the memory object graph, which is treated as assignment to whoever receives that pointer

- merging of control flow reconciles the state of each variable based on the states they have from each edge

Borrowers can be Owners

Borrowers are considered Owners if they are initialized from other than a pointer.

@live void uhoh()
{
scope p = malloc();  // p is considered an Owner
scope const pc = malloc(); // pc is not considered an Owner
} // dangling pointer pc is not detected on exit

Exceptions

The analysis assumes no exceptions are thrown.

@live void leaky()
{
auto p = malloc();
pitcher();  // throws exception, p leaks
free(p);
}

One solution is to use scope(exit):

@live void waterTight()
{
auto p = malloc();
scope(exit) free(p);
pitcher();
}

or use RAII objects or call only nothrow functions.

Lazy Parameters

Lazy parameters are not considered.

Mixing Memory Pools

Conflation of different memory pools:

void* xmalloc(size_t);
void xfree(void*);

void* ymalloc(size_t);
void yfree(void*);

auto p = xmalloc(20);
yfree(p);  // should call xfree() instead

is not detected.

This can be mitigated by using type-specific pools:

U* umalloc();
void ufree(U*);

V* vmalloc();
void vfree(V*);

auto p = umalloc();
vfree(p);  // type mismatch

and perhaps disabling implicit conversions to void* in @live functions.

Variadic Function Arguments

Arguments to variadic functions (such as printf) are considered to be consumed.

References

1. Race-free Multithreading: Ownership

Windows Programming

This covers Windows programming with 32 bit MSCOFF, 64 bit MSCOFF, console programs, GUI programs, and DLLs.

All Console Programs

A D console program is specified by having a function with D linkage at module level called main. When the compiler sees this, it triggers the mixin template core.internal.entrypoint._d_cmain to be added. The _d_cmain template looks like:

template _d_cmain()
{
extern(C)
{
int _d_run_main(int argc, char **argv, void* mainFunc);

int _Dmain(char[][] args);

int main(int argc, char **argv)
{
return _d_run_main(argc, argv, &_Dmain);
}

// Solaris, for unknown reasons, requires both a main() and an _main()
version (Solaris)
{
int _main(int argc, char** argv)
{
return main(argc, argv);
}
}
}
}

The main function in it, because it is marked as extern(C), is the entry point of a C console program. The executable starts up as a C executable, with the C runtime library initialization and then the C main() function is called.

The C runtime library runs any D functions tagged with pragma(crt_constructor) as part of its initialization, and before it calls C main(). The order in which these are run is not specified.

The C main function then calls the D runtime library function _d_run_main, passing it argc and argv, and the address of _Dmain. The compiler renames the D main function in the source code to _Dmain.

_d_run_main then initializes the D runtime library, converts argc and argv to args, and calls the D main function.

When the D main function returns, control is back in _d_run_main which shuts down the D runtime library, and then returns to the C main, which then returns to the C library which shuts down the C runtime library, then exits the program.

The C runtime library runs any D functions tagged with pragma(crt_destructor) as part of its shutdown, in the reverse order that the pragma(crt_constructor)s were run.

Windows 32 and 64 bit MSCOFF Programs

32 bit and 64 bit MSCOFF programs use the Microsoft Visual C/C++ compiler as the Associated C Compiler, generate object files in the MSCOFF format, and use the Microsoft linker to link them.

Console Programs

Windows GUI Programs

DLLs

Glossary

ACC (Associated C Compiler)

The D compiler is matched to the Associated C Compiler. For example, on Win32 ImportC is matched to the Digital Mars C compiler, and can be matched to the Visual C compiler using the command line switch -m32mscoff. Win64 ImportC is matched to the Visual C compiler. On Posix targets, the matching C compiler is Gnu C or Clang C.

BLIT (Block Transfer)

Also known as BLT, blit refers to copying memory byte for byte. In C, this is referred to as a memcpy operation. The name originated with the BLT instruction on the DEC PDP-10 computer.

CTFE (Compile Time Function Evaluation)

Refers to the ability to execute regular D functions at compile time rather than at run time.

Code Point

In Unicode terminology, a code point is a logical character. The range of code points is 0 through 0x10FFFF. Only dchars can store code points directly; arrays of char and wchar need to use the variable-length encodings UTF-8 and UTF-16.

COW (Copy On Write)

COW is a memory management strategy where (usually large) reference objects are copied if they are to be modified. The constant overhead of COW can be high, but may be preferable in applications that would otherwise do a lot of preemptive copying.

Data Race

Two or more threads writing to the same memory location. Program behavior may depend on the arbitrary sequencing of these memory accesses.

Functor

A user-defined type (struct or class) that defines the function call operator (opCall in D) so it can be used similarly to a function.

GC (Garbage Collection)

Garbage collection is the common name for the term automatic memory management. Memory can be allocated and used, and the GC will automatically free any chunks of memory no longer referred to. In contrast, explicit memory management is where the programmer must carefully match up each allocation with one and only one call to free().

Higher-Order Function

A function that either accepts another function as a parameter, returns a function, or both.

IES (Interpolated Expression Sequence)

A value sequence of interspersed string literals and expressions. See spec.

IFTI (Implicit Function Template Instantiation)

Refers to the ability to instantiate a template function without having to explicitly pass in the types to the template. Instead, the types are inferred automatically from the types of the runtime arguments. See spec for details.

Illegal

A code construct is illegal if it does not conform to the D language specification. This may be true even if the compiler or runtime fails to detect the error.

Input Range

A type (i.e., a struct or a class) that defines the member functions empty, front, and popFront. Input ranges are assumed to be strictly one-pass: there is no way to save the state of the iteration in a copy of the range. See also std.range.

Implementation-Defined Behavior

This is variation in behavior of the D language in a manner that is up to the implementor of the language. An example of implementation defined behavior would be the size in bytes of a pointer: on a 32 bit machine it would be 4 bytes, on a 64 bit machine it would be 8 bytes. Minimizing implementation defined behavior in the language will maximize the portability of code.

Lvalue

An lvalue is an abstract term referring to a value with an accessible address (through e.g. the unary & operator). Typical examples of lvalues include variables, constants introduced with const or immutable (but not enum), and elements of arrays and associative arrays. Calls to functions that return ref and pointer dereference expressions are also lvalues. Lvalues can be passed to (or be returned from) functions by reference. An lvalue is the counterpart to an rvalue.

NRVO (Named Return Value Optimization)

NRVO is a technique invented by Walter Bright around 1991 (the term for it was coined later) to minimize copying of struct data. Functions normally return their function return values in registers. For structs, however, they often are too big to fit in registers. The usual solution to this is to pass to the function a hidden pointer to a struct instance in the caller's stack frame, and the return value is copied there. For example:

struct S { int a, b, c, d; }

S foo()
{
S result;
result.a = 3;
return result;
}

void test()
{
S s = foo();
}

is rewritten as:

S* foo(S* hidden)
{
S result;
result.a = 3;
*hidden = result;
return hidden;
}

void test()
{
S tmp;
S s = *foo(&tmp);
}

This rewrite gives us an extra temporary object tmp, and copies the struct contents twice. What NRVO does is recognize that the sole purpose of result is to provide a return value, and so all references to result can be replaced with *hidden. foo is then rewritten as:

S* foo(S* hidden)
{
hidden.a = 3;
return hidden;
}

A further optimization is done on the call to foo to eliminate the other copy, giving:

void test()
{
S s;
foo(&s);
}

The result is written directly into the destination s, instead of passing through two other instances.

Narrow Strings

All arrays that use char, wchar, and their qualified versions are narrow strings. (Those include string and wstring). Range-oriented functions in the standard library handle narrow strings specially by automatically decoding the UTF-encoded characters.

opApply

A special member function used to iterate over a collection; this is used by the ForeachStatement.

opApplyReverse

A special member function used to iterate over a collection in the reverse order; this is used by the ForeachStatement.

POD (Plain Old Data)

Refers to a struct that contains no hidden members, has no destructor, and can be initialized and copied via simple bit copies. See Structs.

Predicate

A function or delegate returning a Boolean result. Predicates can be nullary (take no arguments), unary (take one argument), binary (take two arguments), or n-ary (take n arguments). Usually predicates are mentioned within the context of higher-order functions, which accept predicates as parameters.

Qualifier-Convertible

A type T is qualifer-convertible to type U if and only if they only differ in qualifiers and if a reference to T can be implicitly converted to a reference to U per the language rules.

RAII (Resource Acquisition Is Initialization)

RAII refers to the technique of having the destructor of a struct or class object called when the object goes out of scope. The destructor then releases any resources acquired by that object. RAII is commonly used for resources that are in short supply or that must have a predictable point when they are released. RAII can be used for class variables declared with the scope storage class.

Rvalue

An rvalue is an abstract term referring to a value resulting from an expression that has no accessible memory address. This lack of address is only conceptual; the implementation has the liberty to store an rvalue in addressable memory. Rvalues cannot be changed and cannot be passed to (or be returned from) functions by reference. An rvalue is the counterpart to an lvalue.

Sequential Consistency

Data being written in one order in one thread being visible in the same order to another thread.

SFINAE (Substitution Failure Is Not An Error)

If template argument deduction results in a type that is not valid, that specialization of the template is not considered further. It is not a compile error. See also SFINAE.

TMP (Template Metaprogramming)

TMP is using the template features of the language to execute programs at compile time rather than runtime.

TLS (Thread Local Storage)

TLS allocates each thread its own instance of the global data. See also Wikipedia.

UB (Undefined Behavior)

Undefined behavior happens when an illegal code construct is executed. Undefined behavior can include random, erratic results, crashes, faulting, etc. A buffer overflow is an example of undefined behavior.

UDA (User-Defined Attribute)

User-defined attributes are metadata attached to symbols used for compile-time reflection.

UFCS (Uniform Function Call Syntax)

Uniform function call syntax refers to the ability to call a function as if it were a method of its first argument. For example funct(arg) may be written as arg.funct().

Legacy Code

To maintain compatibility with older D code, many legacy features remain supported. This page describes each legacy feature that is supported, with a suggestion of how to modernize the code.

Legacy Features

Feature

Summary

body keyword

body after a contract statement - use do instead

alias target first syntax

use alias name = target instead.

Struct/union postblit

use a copy constructor instead.

Aliasing an instance member

use typeof(instance).member instead.

body keyword

body was a keyword used to specify a function/method's body after a contract statement:

class Foo
{
void bar(int i)
in { assert(i >= 42); }
body { /* Do something interesting */ }

string method(string s)
out(v) { assert(v.length == s.length); }
body { /* Do something even more interesting */ }

void noBody() { /* No contracts, no body */ }
}

Corrective Action

Use the do keyword instead (introduced in v2.075.0):

void bar(int i)
in { assert(i >= 42); }
do { /* Look ma, no body! */ }

Rationale: The body keyword was only used for this single purpose. Since D grammar aims to be context free, this common word was reserved, which led to frequent trouble for people interfacing with other languages (e.g. javascript) or auto-generating code.

Aliasing an instance member

E.g. alias a = instance.field;. Such an alias actually aliases a member of the instance's type, not the instance member itself. That could be confusing. Instead, alias a member of the type.

Note: Aliasing this.member and super.member are valid inside methods.

struct Bar
{
Foo f;
alias v = f.v; // Error, use `typeof(f).v`
}

struct Foo
{
int v;
void test(Foo that) const
{
alias a = this.v; // OK
alias b = that.v; // Error, use `typeof(that).v` instead
assert(&a is &b); // passes
assert(&b !is &that.v);
}
}