Sometimes it becomes clear that a feature is just a bad idea. These are calleddeprecated 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.
Throwing exceptions from the contracts of anothrow function was permitted:
float sqrt(float n)nothrowin{if (n < 0)thrownew Exception("n must be positive");}do{// ...}
Remove thenothrow attribute or rewrite the contract usingassertions instead.
float sqrt(float n)nothrowin{assert(n >= 0);}do{// ...}
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 thenothrow attribute.
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;
Move the (de)allocation strategy out of the class
class Foo{}T make(T, Args...)(autoref Args args)if (is(T == Foo)){enum size =__traits(classInstanceSize, T);void* mem = malloc(size);scope (failure) free(mem);return mem !isnull ? 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 !isnull) dispose(foo);
Classes should not be responsible for their own (de)allocation strategy.
Comparison of different enumerated type was allowed:
enum Status{ good, bad}enum OtherStatus{ ok, no}staticassert(Status.good == OtherStatus.ok);
Comparison between unrelated enumerated types should be done withstd.conv.asOriginalType
import std.conv : asOriginalType;assert(Foo.x.asOriginalType == Bar.y.asOriginalType);
Code correctness is improved by disallowing comparison of unrelated enumerated types. Implicit comparison of differentenum types often resulted in hard to spot bugs.
enum { X }enum { Y }void main() {auto b = X == Y;assert(b);}
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
Replace implicit string concatenation by explicit one, using '~'.
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" ]
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;// trueIt'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)If possible, split the comma operator in two statements. Otherwise use lambdas.
auto result = foo(), bar();// split off in two statementsfoo();auto result = bar();// or use lambdasauto result = {foo();return bar();}();
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, 9template vec(T...)(T args) { ... }vec v = (0, 0, 3);// 3, because vec is variadicint 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
Memory allocated on the GC heap can be freed withdelete.
auto a =new Class();delete a;
Useobject.destroy() to finalize the object instead.
auto a =new Class();destroy(a);
Note thatdestroy does not free the allocated memory. If necessary, callcore.GC.free also.
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.
Thescope keyword can be added to a class declaration to force all instances of the class to be attributed with thescope storage class.
scopeclass 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.}
scope as a type constraint was a quirk in the language without a compelling use case.
Note that this deprecation only affects the usage ofscope as a type constraint attributed to a class declaration.scope as a storage class attributed to variables, function parameters, etc. is not deprecated.
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);
Use the library types instd.complex.
These types are too specialized to be a part of the core language.
D currently reserves thecent anducent keywords for future use as 128-bit integral types. Using them will result in a compile-time error.
cent a = 18446744073709551616L;ucent b = 36893488147419103232UL;
Use the library types instd.int128 orcore.int128.
These types are too specialized to be a part of the core language.
One can catch everything by usingcatch without specifying a type.
int[] arr =newint[](10);// This will throw a RangeErrortry { arr[42]++; }catch { writeln("An error was caught and ignored"); }
Either don't catchThrowable or replacecatch {} withcatch (Throwable) {}
int[] arr =newint[](10);// This will throw a RangeErrortry { arr[42]++; }catch (Throwable) { writeln("An error was caught and ignored"); }
CatchingThrowable 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 catchingThrowable is always a conscious and visible decision on the programmer's side.
D arrays can be manipulated using these built-in properties.
int[] x = [2, 3, 1];assert(x.sort == [1, 2, 3]);
Use the generic functions instd.algorithm.
These operations are better implemented in the standard library.
C-style array pointers can be used in D.
aliasfloat *arrayptr[10][15];
Replace with D-style array pointers.
aliasfloat[15][10]* arrayptr;
The D syntax is much cleaner and easier to use.
D currently supports the NCEG floating point operators (!<>=, <>, <>=, !>, !>=, !<, !<=, !<>) for comparisons involving NaNs.
Use the normal operators andstd.math.traits.isNaN.
These operators are too specialized to be a part of the core language.
Call an object's destructor.
auto a =new Class();clear(a);
Useobject.destroy() instead.
auto a =new Class();destroy(a);
Due to Uniform Function Call Syntax (UFCS),clear can cause confusion with other methods of the same name, such as aclear method used to remove the contents of a container.
Floating point types have the .min property to access the smallest value.
enum m =real.min;
Replace with .min_normal
enum m =real.min_normal;
The name min_normal is more accurate, as .min does not include denormalized floating point values.
At some point in time you could do:
ulong u =cast(ulong)[1,2];To get the length of the array.
Use the.length property instead.
Using a cast to get the length of an array is just confusing.
Base class protections are things like:
class A :protected B{ ...}
Delete the protection attribute keyword from in front of the base class and base interfaces.
With D's module system, it doesn't seem to serve any useful purpose, and has never worked correctly.
There is some code in Phobos for Windows 3.x/9x support.
Upgrade Windows or switch to another supported OS.
Supporting such outdated and rarely used OS-es isn't worth the trouble.
typedef can be used to construct a strongly-typed alias of a type.
typedefint myint;staticassert(!is(myint ==int));
Replace use of typedef with alias or usestd.typecons.Typedef.
typedef is not flexible enough to cover all use cases. This is better done with a library solution.
D array variables can be dereferenced to get the first element, much like pointers.
int[] arr = [1, 2, 3];assert(*arr == 1);
Use indexing syntax to access first member.
int[] arr = [1, 2, 3];assert(arr[0] == 1);
D arrays are not pointers.
The invariant storage class and type modifier is an alias for immutable.
staticassert(is(invariant(int) ==immutable(int)));
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.
The alias is unnecessary.
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();}
Add the default case manually.
switch(a){case 1:break;case 2:break;default:assert(0);}
Missing default cases can hide bugs, and making the default case explicit should be mandatory.
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) {}}
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}
This is an error that is already detected at runtime, and is being extended to compile time.
Octal literals can be used to enter literals in base 8.
// deprecated code// auto x = 0123;
Use thestd.conv.octal template.
auto x = octal!123;The use of a leading zero is confusing, as 0123 != 123.
C-style function pointers can be used in D.
aliasvoid(*fptr)(int,long);
Replace with D-style function pointers.
aliasvoidfunction(int,long) fptr;
The D syntax is much cleaner and easier to use.
When used inside an indexing or slicing expression, length is rewritten to be the length of the array being sliced.
auto a =newint[5];a = a[0..length-1];
Replace length with the equivalent'$'
The implicitly defined length variable shadows existing declarations, and is less concise than the alternative.
Escape string literals can be used to describe characters using escape sequences.
// deprecated code// string x = "hello" ~ \n;
Put escape sequences inside a regular string literal.
string x ="hello\n";Escape string literals are unintuitive and unnecessary.
volatile can be used to mark statement, in order to prevent some compiler optimizations.
volatile{ ...do something involving ghared variables ...}Convert the code to use synchronized statements instead.
volatile statements are a misfeature.
The D compiler can parse html files by ignoring everything not contained in <code></code> tags.
<html><code> ... source ...</code></html>
Extract code to regular source files.
This has been replaced for documentation by the introduction of ddoc
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 overridevoid fun() {}}
Mark overriding functions withoverride
class A{void fun() {}}class B : A{overridevoid fun() {}}
Making theoverride attribute mandatory makes it explicit, and can catch errors when a base class function is accidentally overridden.
Lower case 'l' is an alternative suffix to denote 64 bit integer literals.
// deprecated code// auto x = 123l;
Use the upper case 'L' suffix.
auto x = 123L;The lower case suffix is easily confused with the digit '1'.
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 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? }}
Rename shadowing variables so they don't conflict.
Variable shadowing can introduce hard to find bugs where the wrong variable is modified.
The 'I' suffix can be used to denote imaginary floating point values.
// deprecated code// auto x = 1.234I;
Use the lower case 'i' suffix.
auto x = 1.234i;The 'I' suffix is easily confused with the digit '1'.
This syntax can be used to declare a variable in an if statement condition.
if (v; calculateAndReturnPointer()) { ... }Replace with an auto declaration.
if (auto v = calculateAndReturnPointer()) { ... }
The syntax is clearer with auto.
delete can be used to remove an item from an associative array.
int[int] aa = [1 : 2];delete aa[1];assert(1 !in aa);
Use .remove instead.
int[int] aa = [1 : 2];aa.remove(1);assert(1 !in aa);
The delete syntax is confusing and conflicts with the normal delete syntax.
The .offset property can be used to get member offset information.
struct S {int a, b; }staticassert(S.b.offset == 4);
Use .offsetof instead
struct S {int a, b; }staticassert(S.b.offsetof == 4);
The .offset syntax has been superseded by .offsetof
The .size property can be used to get type size information
struct S {int a, b; }staticassert(S.size == 8);
Use .sizeof instead
struct S {int a, b; }staticassert(S.sizeof == 8);
The .size syntax has been superseded by .sizeof
The .typeinfo property can be used to get the associated TypeInfo class.
T.typeinfo
Use typeid() instead
typeid(T)The .typeinfo syntax has been superseded by typeid()
asm blocks don't affect the function annotations.
void foo() @safe{asm { noop; }}
Annotate theasm blocks instead
void foo() @safe{asm @trusted { noop; }}
asm blocks may throw, contain unsafe, impure code or call the GC interfaces.
Previously, animmutable,const,inout orshared exception could bethrown and then caught in an unqualifiedcatch (Exception e) clause.That breaks type safety.Throwing a qualified object is now deprecated. This helps to preventpossible mutation of an immutable object in acatch clause.
The runtime also modifies a thrown object (e.g. to contain a stacktrace) which can violateconst orimmutable objects. Throwingqualified objects has been deprecated for this reason also.
It is unsafe to catch an exception asimmutable,inout orshared.This is because the exception may still be accessible through anothermutable 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}