Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

Deprecated Features

Sometimes it becomes clear that a feature is just a bad idea. These are called deprecated features and once they are judged to merit removal from the language, they follow a procedure in order to allow plenty of time for users to adjust to the change.

Deprecated Features
Feature Spec Dep Error Gone
Throwing from contracts of nothrow functions  2.1012.111 
2.079 2.0792.086 
Class allocators and deallocators  2.0802.0872.100
Implicit comparison of different enums 2.075 2.0752.081 
Implicit string concatenation 2.072 2.0722.081 
Using the result of a comma expression 2.072 2.072 2.079 
delete  2.0792.0992.109
scope as a type constraint  2.087  
Imaginary and complex types future2.097  
128-bit integer types future2.1002.100 
Implicit catch statement 2.0722.072 2.081 
.sort and .reverse properties for arrays ? 2.072  2.075
C-style array pointers ? 2.072 2.082 
Floating point NCEG operators 2.0792.066 2.072 2.080
clear 2.0602.066  2.068
.min property for floating point types N/A 2.065 2.067 2.072
Cast T[] to integral type ? 2.060  2.061
Base Class Protection 2.0582.058 2.067 2.072
Windows 3.x and Windows 9x support 2.058N/A N/A 2.058
typedef 2.0572.057 2.067 2.072
Using * to dereference arrays ? 2.057 2.067 (never)
invariant as an alias for immutable 2.0572.057 2.064 2.066
Non-final switch statements without a default case 2.0542.054 2.068 (never)
Hiding base class functions 2.0542.054 2.068 (never)
Octal literals 2.0542.053 2.067 (never)
C-style function pointers ? 2.050 2.067 (never)
Using length in index expressions ? 2.041  2.061
Escape string literals ? 2.026 2.061 2.067
volatile 2.0132.013 2.067 2.072
HTML source files ? 2.013 N/A 2.061
Overriding without override ? 2.004 2.072 (never)
Lower case 'l' suffix for integer literals ? 1.054 0.174 (never)
Variable shadowing inside functions ? 0.161  2.061
Upper case 'I' suffix for imaginary literals ? 0.154 2.061 (never)
if (v; e) ? 0.149 2.061 2.068
Removing an item from an associative array with delete? 0.127 2.061 2.067
.offset property ? 0.107 2.061 2.067
.size property ? 0.107 0.107 2.061
.typeinfo property ? 0.093 2.061 2.067
unannotated asm blocks   2.100 (never)
Throwing qualified objects  2.104  (never)
Catching immutable/inout/shared objects  2.106  (never)
Spec
Removal from the Specification
Dep
The compiler warns by default, issues an error with the -de switch, and can be silenced with the -d switch
Error
It is an error to use the feature
Gone
The feature is completely gone

Throwing from contracts of nothrow functions

Throwing exceptions from the contracts of a nothrow function was permitted:

float sqrt(float n) nothrow
in
{
    if (n < 0)
        throw new Exception("n must be positive");
}
do
{
    // ...
}

Corrective Action

Remove the nothrow attribute or rewrite the contract using assertions instead.

float sqrt(float n) nothrow
in
{
    assert(n >= 0);
}
do
{
    // ...
}

Rationale

Since a function's preconditions and postconditions are implicitly executed before and after the function's body, allowing them to throw would break the guarantees of the nothrow attribute.

Class allocators and deallocators

D classes can have members customizing the (de)allocation strategy.

class Foo
{
    new(uint size, ...)
    {
        return malloc(size);
    }

    delete(void* obj)
    {
        free(obj);
    }
}

Foo foo = new(...) Foo();
delete foo;

Corrective Action

Move the (de)allocation strategy out of the class

class Foo
{
}

T make(T, Args...)(auto ref Args args) if (is(T == Foo))
{
    enum size = __traits(classInstanceSize, T);
    void* mem = malloc(size);
    scope (failure) free(mem);
    return mem !is null ? emplace!T(mem[0..size], args) : null;
}

void dispose(T)(T obj)
{
    auto mem = cast(void*) obj;
    scope (exit) free(mem);
    destroy(obj);
}

Foo foo = make!Foo();
if (foo !is null) dispose(foo);

Rationale

Classes should not be responsible for their own (de)allocation strategy.

Implicit comparison of different enums

Comparison of different enumerated type was allowed:

enum Status
{
    good,
    bad
}
enum OtherStatus
{
    ok,
    no
}
static assert(Status.good == OtherStatus.ok);

Corrective Action

Comparison between unrelated enumerated types should be done with std.conv.asOriginalType

import std.conv : asOriginalType;
assert(Foo.x.asOriginalType == Bar.y.asOriginalType);

Rationale

Code correctness is improved by disallowing comparison of unrelated enumerated types. Implicit comparison of different enum types often resulted in hard to spot bugs.

enum { X }
enum { Y }
void main() {
    auto b = X == Y;
    assert(b);
}

Implicit string concatenation

Two adjacent strings were implicitly concatenated:

string foo = "Hello" "World";
This feature was handy for a long string that spans multiple lines, however, it is possible to get the same behaviour explicitly by using the concatenation operator ('~'):
string foo = "Hello" ~ "World"; // No allocation is performed

Corrective Action

Replace implicit string concatenation by explicit one, using '~'.

Rationale

This is a very early feature of the language, which is nowadays totally covered by the concatenation operator: it is performed at compile time for constants and doesn't result in memory allocation.

However, having implicit concatenation can and did result in hard to spot bugs, for example:

string[] names =
[
    "Anna",
    "Michael"
    "Emma",
    "David"
];
// The content of arr is [ "Anna", "MichaelEmma", "David" ]

Using the result of a comma expression

The comma operator (,) allows executing multiple expressions and discards the result of them except for the last which is returned.

int a = 1;
int b = 2;
bool ret = a == 2, b == 2; // true
It's also common to use the comma operator in for-loop increment statements to allow multiple expressions.
for (; !a.empty && !b.empty; a.popFront, b.popFront)

Corrective Action

If possible, split the comma operator in two statements. Otherwise use lambdas.

auto result = foo(), bar();

// split off in two statements
foo();
auto result = bar();

// or use lambdas
auto result = {foo(); return bar();}();

Rationale

The comma operator leads to unintended behavior (see below for a selection) Moreover it is not commonly used and it blocks the ability to implement tuples. A selection of problems through the accidental use of the comma operator:

writeln( 6, mixin("7,8"), 9 ); // 6, 8, 9

template vec(T...)(T args) { ... }
vec v = (0, 0, 3); // 3, because vec is variadic

int b = 2;
if (a == 1, b == 2) {
    // will always be reached
}

void foo(int x) {}
foo((++a, b));

synchronized (lockA, lockB) {}
// isn't currently implemented, but still compiles due to the comma operator

delete

Memory allocated on the GC heap can be freed with delete.

auto a = new Class();
delete a;

Corrective Action

Use object.destroy() to finalize the object instead.

auto a = new Class();
destroy(a);

Note that destroy does not free the allocated memory. If necessary, call core.GC.free also.

Rationale

delete makes assumptions about the type of garbage collector available that limits which implementations can be used, and can be replaced by a library solution.

scope as a type constraint

The scope keyword can be added to a class declaration to force all instances of the class to be attributed with the scope storage class.

scope class C { }          // `scope` type constraint.  This usage of `scope` is deprecated.

void main()
{
    C c1 = new C();        // Error: reference to `scope class` must be `scope`
                           // This error is due to the `scope` attribution on the declaration
                           // of `class C` and the missing `scope` storage class attribution
                           // on `c1`.

    scope C c2 = new C();  // OK because the instance `c2` is attributed with the `scope`
                           // storage class.  This usage of `scope` is not deprecated.
}

Corrective Action

There is no current counterpart in the D programming language or library that places such a constraint on a type requiring all instances of the type to be attributed with the scope storage class.

Rationale

scope as a type constraint was a quirk in the language without a compelling use case.

Note that this deprecation only affects the usage of scope as a type constraint attributed to a class declaration. scope as a storage class attributed to variables, function parameters, etc. is not deprecated.

Imaginary and complex types

D currently supports imaginary and complex versions of all floating point types.

float a = 2;
ifloat b = 4i;
cfloat c = a + b;
assert(c == 2 + 4i);

Corrective Action

Use the library types in std.complex.

Rationale

These types are too specialized to be a part of the core language.

128-bit integer types

D currently reserves the cent and ucent keywords for future use as 128-bit integral types, but the use of will result in a compile-time error.

cent a = 18446744073709551616L;
ucent b = 36893488147419103232UL;

Corrective Action

Use the library types in core.int128.

Rationale

These types are too specialized to be a part of the core language.

Implicit catch statement

One can catch everything by using catch without specifying a type.

int[] arr = new int[](10);
// This will throw a RangeError
try { arr[42]++; }
catch { writeln("An error was caught and ignored"); }

Corrective Action

Either don't catch Throwable or replace catch {} with catch (Throwable) {}

int[] arr = new int[](10);
// This will throw a RangeError
try { arr[42]++; }
catch (Throwable) { writeln("An error was caught and ignored"); }

Rationale

Catching Throwable should not be encouraged by the language, because certain core guarantee cannot be satisfied, e.g. the stack might not get cleaned up and destructors might not get run. This change helps ensure catching Throwable is always a conscious and visible decision on the programmer's side.

.sort and .reverse properties for arrays

D arrays can be manipulated using these built-in properties.

int[] x = [2, 3, 1];
assert(x.sort == [1, 2, 3]);

Corrective Action

Use the generic functions in std.algorithm.

Rationale

These operations are better implemented in the standard library.

C-style array pointers

C-style array pointers can be used in D.

alias float *arrayptr[10][15];

Corrective Action

Replace with D-style array pointers.

alias float[15][10]* arrayptr;

Rationale

The D syntax is much cleaner and easier to use.

Floating point NCEG operators

D currently supports the NCEG floating point operators (!<>=, <>, <>=, !>, !>=, !<, !<=, !<>) for comparisons involving NaNs.

Corrective Action

Use the normal operators and std.math.traits.isNaN.

Rationale

These operators are too specialized to be a part of the core language.

clear

Call an object's destructor.

auto a = new Class();
clear(a);

Corrective Action

Use object.destroy() instead.

auto a = new Class();
destroy(a);

Rationale

Due to Uniform Function Call Syntax (UFCS), clear can cause confusion with other methods of the same name, such as a clear method used to remove the contents of a container.

.min property for floating point types

Floating point types have the .min property to access the smallest value.

enum m = real.min;

Corrective Action

Replace with .min_normal

enum m = real.min_normal;

Rationale

The name min_normal is more accurate, as .min does not include denormalized floating point values.

Cast T[] to integral type

At some point in time you could do:

ulong u = cast(ulong)[1,2];
To get the length of the array.

Corrective Action

Use the .length property instead.

Rationale

Using a cast to get the length of an array is just confusing.

Base Class Protection

Base class protections are things like:

class A : protected B
{
    ...
}

Corrective Action

Delete the protection attribute keyword from in front of the base class and base interfaces.

Rationale

With D's module system, it doesn't seem to serve any useful purpose, and has never worked correctly.

Windows 3.x and Windows 9x support

There is some code in Phobos for Windows 3.x/9x support.

Corrective Action

Upgrade Windows or switch to another supported OS.

Rationale

Supporting such outdated and rarely used OS-es isn't worth the trouble.

typedef

typedef can be used to construct a strongly-typed alias of a type.

typedef int myint;
static assert(!is(myint == int));

Corrective Action

Replace use of typedef with alias or use std.typecons.Typedef.

Rationale

typedef is not flexible enough to cover all use cases. This is better done with a library solution.

Using * to dereference arrays

D array variables can be dereferenced to get the first element, much like pointers.

int[] arr = [1, 2, 3];
assert(*arr == 1);

Corrective Action

Use indexing syntax to access first member.

int[] arr = [1, 2, 3];
assert(arr[0] == 1);

Rationale

D arrays are not pointers.

invariant as an alias for immutable

The invariant storage class and type modifier is an alias for immutable.

static assert(is(invariant(int) == immutable(int)));

Corrective Action

Replace all uses of invariant as a storage class or type modifier with immutable. The invariant() syntax for struct and class invariants is still supported.

Rationale

The alias is unnecessary.

Non-final switch statements without a default case

Switch statements can be declared without a default case, and the compiler automatically adds one.

switch(a)
{
case 1:
    break;
case 2:
    break;
// the compiler adds
// default:
//     throw new SwitchError();
}

Corrective Action

Add the default case manually.

switch(a)
{
case 1:
    break;
case 2:
    break;
default:
    assert(0);
}

Rationale

Missing default cases can hide bugs, and making the default case explicit should be mandatory.

Hiding base class functions

This occurs when declaring a function in a derived class that can be called with the same arguments as a function in a base class, without overriding that function. The base class function gets hidden:

class A
{
    void fun(int x) {}
}
class B : A
{
    void fun(long x) {}
}

Corrective Action

Add the function to the base class, or use an alias to bring the base class overload into the derived class:

class A
{
    void fun(int x) {}
    void fun(long x) {} // this fixes it
}
class B : A
{
    void fun(long x) {}
    alias A.fun fun; // so does this
}

Rationale

This is an error that is already detected at runtime, and is being extended to compile time.

Octal literals

Octal literals can be used to enter literals in base 8.

// deprecated code
// auto x = 0123;

Corrective Action

Use the std.conv.octal template.

auto x = octal!123;

Rationale

The use of a leading zero is confusing, as 0123 != 123.

C-style function pointers

C-style function pointers can be used in D.

alias void(*fptr)(int, long);

Corrective Action

Replace with D-style function pointers.

alias void function(int, long) fptr;

Rationale

The D syntax is much cleaner and easier to use.

Using length in index expressions

When used inside an indexing or slicing expression, length is rewritten to be the length of the array being sliced.

auto a = new int[5];
a = a[0..length-1];

Corrective Action

Replace length with the equivalent '$'

Rationale

The implicitly defined length variable shadows existing declarations, and is less concise than the alternative.

Escape string literals

Escape string literals can be used to describe characters using escape sequences.

// deprecated code
// string x = "hello" ~ \n;

Corrective Action

Put escape sequences inside a regular string literal.

string x = "hello\n";

Rationale

Escape string literals are unintuitive and unnecessary.

volatile

volatile can be used to mark statement, in order to prevent some compiler optimizations.

volatile
{
    ... do something involving ghared variables ...
}

Corrective Action

Convert the code to use synchronized statements instead.

Rationale

volatile statements are a misfeature.

HTML source files

The D compiler can parse html files by ignoring everything not contained in <code></code> tags.

<html>
<code>
    ... source ...
</code>
</html>

Corrective Action

Extract code to regular source files.

Rationale

This has been replaced for documentation by the introduction of ddoc

Overriding without override

Virtual functions can currently override a function in a base class without the 'override' attribute.

class A
{
    void fun() {}
}
class B : A
{
    // overrides but is not marked with override
    void fun() {}
}

Corrective Action

Mark overriding functions with override

class A
{
    void fun() {}
}
class B : A
{
    override void fun() {}
}

Rationale

Making the override attribute mandatory makes it explicit, and can catch errors when a base class function is accidentally overridden.

Lower case 'l' suffix for integer literals

Lower case 'l' is an alternative suffix to denote 64 bit integer literals.

// deprecated code
// auto x = 123l;

Corrective Action

Use the upper case 'L' suffix.

auto x = 123L;

Rationale

The lower case suffix is easily confused with the digit '1'.

Note

In lexical analysis phase, compiler can recognize lower case suffix 'l' to report better error message - for the use case such as C-to-D code translation. Thus DMD would continue to parse 'l' suffix.

Variable shadowing inside functions

Variable shadowing is when a variable in an inner scope has the same name as a variable in an enclosing scope.

void myFun()
{
    int var;
    if (x)
    {
        int var;
        var = 3; // which var was meant?
    }
}

Corrective Action

Rename shadowing variables so they don't conflict.

Rationale

Variable shadowing can introduce hard to find bugs where the wrong variable is modified.

Upper case 'I' suffix for imaginary literals

The 'I' suffix can be used to denote imaginary floating point values.

// deprecated code
// auto x = 1.234I;

Corrective Action

Use the lower case 'i' suffix.

auto x = 1.234i;

Rationale

The 'I' suffix is easily confused with the digit '1'.

if (v; e)

This syntax can be used to declare a variable in an if statement condition.

if (v; calculateAndReturnPointer()) { ... }

Corrective Action

Replace with an auto declaration.

if (auto v = calculateAndReturnPointer()) { ... }

Rationale

The syntax is clearer with auto.

Removing an item from an associative array with delete

delete can be used to remove an item from an associative array.

int[int] aa = [1 : 2];
delete aa[1];
assert(1 !in aa);

Corrective Action

Use .remove instead.

int[int] aa = [1 : 2];
aa.remove(1);
assert(1 !in aa);

Rationale

The delete syntax is confusing and conflicts with the normal delete syntax.

.offset property

The .offset property can be used to get member offset information.

struct S { int a, b; }
static assert(S.b.offset == 4);

Corrective Action

Use .offsetof instead

struct S { int a, b; }
static assert(S.b.offsetof == 4);

Rationale

The .offset syntax has been superseded by .offsetof

.size property

The .size property can be used to get type size information

struct S { int a, b; }
static assert(S.size == 8);

Corrective Action

Use .sizeof instead

struct S { int a, b; }
static assert(S.sizeof == 8);

Rationale

The .size syntax has been superseded by .sizeof

.typeinfo property

The .typeinfo property can be used to get the associated TypeInfo class.

T.typeinfo

Corrective Action

Use typeid() instead

typeid(T)

Rationale

The .typeinfo syntax has been superseded by typeid()

unannotated asm blocks

asm blocks don't affect the function annotations.

void foo() @safe
{
    asm { noop; }
}

Corrective Action

Annotate the asm blocks instead

void foo() @safe
{
    asm @trusted { noop; }
}

Rationale

asm blocks may throw, contain unsafe, impure code or call the GC interfaces.

Throwing qualified objects

Previously, an immutable, const, inout or shared exception could be thrown and then caught in an unqualified catch (Exception e) clause. That breaks type safety. Throwing a qualified object is now deprecated. This helps to prevent possible mutation of an immutable object in a catch clause.

The runtime also modifies a thrown object (e.g. to contain a stack trace) which can violate const or immutable objects. Throwing qualified objects has been deprecated for this reason also.

Catching immutable/inout/shared objects

It is unsafe to catch an exception as immutable, inout or shared. This is because the exception may still be accessible through another mutable or non-shared reference.

auto e = new Exception("first");
try {
        throw e;
} catch(immutable Exception ie) { // unsafe
        e.msg = "second";
        assert(ie.msg == "first"); // would fail
}