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
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 |
0 | signed 128 bits | |
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 |
float.nan*1.0i | imaginary float | |
double.nan*1.0i | imaginary double | |
real.nan*1.0i | imaginary real | |
float.nan+float.nan*1.0i | a complex number of two float values | |
double.nan+double.nan*1.0i | complex double | |
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
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 to type T has a value which is a reference (address) to another object of type T. It is commonly called a pointer to T and its type is T*. To access the object 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 contains a null value, it is not pointing to a valid object.
When a pointer to T is dereferenced, it must either contain a null value, or point to a valid object of type T.
- The behavior when a null pointer is dereferenced. Typically the program will be aborted.
To set a pointer to point at an existing object, 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.
User-Defined Types
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.
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:
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)); }
- 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:
- If either operand is real, the other operand is converted to real.
- Else if either operand is double, the other operand is converted to double.
- Else if either operand is float, the other operand is converted to float.
- Else the integer promotions above are done on each operand,
followed by:
- If both are the same type, no more conversions are done.
- If both are signed or both are unsigned, the smaller type is converted to the larger.
- If the signed type is larger than the unsigned type, the unsigned type is converted to the signed type.
- The signed type is converted to the unsigned type.
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:
- If the operands are the same type, the result will be of that type.
- If one operand is an enum and the other is the base type of that enum, the result is the base type.
- 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; }
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).
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.
- 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, either:
- An object reference and a pointer to a non-static member function.
- A pointer to a closure and a pointer to a nested function. The object reference forms the this pointer when the function is called.
Delegates are declared and initialized similarly to function pointers:
int delegate(int) dg; // dg is a delegate to a function class OB { int member(int); } void f(OB o) { dg = &o.member; // dg is a delegate to object o and member function member }
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)
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 )
typeof is a way to specify a type based on the type of an 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 typeof(1)* p; // p is of type pointer to int int[typeof(p)] a; // a is of type int[int*] writeln(typeof('c').sizeof); // prints 1 double c = cast(typeof(1.0))j; // cast j to double }
Expression is not evaluated, it is used purely to generate the type:
void func() { int i = 1; typeof(++i) j; // j is declared to be an int, i is not incremented writeln(i); // prints 1 }
If Expression is a ValueSeq it will produce a TypeSeq containing the types of each element.
Special cases:
- typeof(return) will, when inside a function scope, give the return type of that function.
- typeof(this) will generate the type of what this would be in a non-static member function, even if not in a member function.
- 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));
- Typeof is most useful in writing generic template code.
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.
A function that never returns has the return type noreturn. This can occur due to an infinite loop or always throwing an exception.
noreturn abort(const(char)[] message); 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 halts execution.
)