The OpenD Programming Language

Types

 Type:
    TypeCtorsopt BasicType TypeSuffixesopt

TypeCtors: TypeCtor

TypeCtor TypeCtors

TypeCtor: const immutable inout shared

BasicType: FundamentalType

. QualifiedIdentifier QualifiedIdentifier

Typeof

Typeof . QualifiedIdentifier TypeCtor ( Type ) Vector

traits, 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: * [ ] [ expression, AssignExpression ] [ expression, AssignExpression .. expression, AssignExpression ] [ Type ] delegate function, Parameters function, MemberFunctionAttributesopt function function, Parameters function, FunctionAttributesopt

QualifiedIdentifier: Identifier Identifier . QualifiedIdentifier template, TemplateInstance

template, TemplateInstance . QualifiedIdentifier Identifier [ expression, AssignExpression ] Identifier [ expression, AssignExpression ] . QualifiedIdentifier

Basic Data Types

Basic Data Types
Keyword Default Initializer (.init) Description
voidno default initializervoid has no value
`bool`falseboolean value
byte0signed 8 bits
ubyte0uunsigned 8 bits
short0signed 16 bits
ushort0uunsigned 16 bits
int0signed 32 bits
uint0uunsigned 32 bits
long0Lsigned 64 bits
ulong0uLunsigned 64 bits
cent0signed 128 bits
ucent0uunsigned 128 bits
floatfloat.nan32 bit floating point
doubledouble.nan64 bit floating point
realreal.nanlargest floating point size available
ifloatfloat.nan*1.0iimaginary float
idoubledouble.nan*1.0iimaginary double
irealreal.nan*1.0iimaginary real
cfloatfloat.nan+float.nan*1.0ia complex number of two float values
cdoubledouble.nan+double.nan*1.0icomplex double
crealreal.nan+real.nan*1.0icomplex 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

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: Complex and imaginary types ifloat, idouble, ireal, cfloat, cdouble, and creal have been deprecated in favor of std.complex.Complex.

Derived Data Types

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.

  1. The behavior when a null pointer is dereferenced. Typically the program will be aborted.
dereferencing a pointer that is not null and does not point to a valid object of type T.

To set a pointer to point at an existing object, use the &amp; <em>address of</em> operator:

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

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

See also Pointer Arithmetic.

User-Defined Types

Type Conversions

See also: expression, CastExpression.

Pointer Conversions

Pointers implicitly convert to void*.

Casting between pointers and non-pointers is allowed. Some pointer casts are disallowed in Memory-Safe-D-Spec.

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.

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
boolint
byteint
ubyteint
shortint
ushortint
charint
wcharint
dcharuint

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 Enums 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).

Preserving Bit Patterns

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 = int(-1);  // ok, -1 can be represented in an int, which can be converted to a uint
ulong  u4 = long(-1); // ok, -1 can be represented in a long, which can be converted to a ulong
  • Floating point types cannot be implicitly converted to integral types.
  • 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 &gt; short.min
    // max is c.max + 100 &lt; short.max
    short s = c + 100; // OK

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

    k = i &amp; b; // OK, 0 ... b.max
    //b = b + b; // error, b.max + b.max &gt; 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 &amp; 0xff; // OK
    // s is now assumed to be s.min ... s.max, not 0 ... 0xff
    //ubyte b = s; // error
    ubyte b = s &amp; 0xff; // OK

    const int c = i &amp; 0xff;
    // c's range is fixed and known
    b = c; // OK
}
  • For more information, see the dmc article.
  • See also: <a href="https://en.wikipedia.org/wiki/Value_range_analysis">https://en.wikipedia.org/wiki/Value_range_analysis</a>.

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 for 0 or !=0 for arithmetic types, and null or !=null for pointers or references.

Function Types

A function type has the form:

declaration, StorageClassesopt Type function, Parameters function, 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 function, Parameters function, FunctionAttributesopt
Type delegate function, Parameters function, 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 = &amp;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, 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 <em>Expression</em> is a ValueSeq it will produce a <em>TypeSeq</em> containing the types of each element.

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 spec/template, typeof gives the type void.

template t {}
static assert(is(typeof(t) == void));
  1. Typeof is most useful in writing generic template code.

Mixin Types

 MixinType:
    mixin ( expression, ArgumentList )

Each expression, 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, 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 &lt; 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.

declaration, Declarations, property, Properties )