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.
- 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
Hexstring literals
Hexstring literals can be used to enter literals in base 16.
// deprecated code // auto x = x"1234";
Corrective Action
Use the std.conv.hexString template.
auto x = hexString!"1234";
Rationale
Hexstrings are used so seldom that they don't warrant a language feature.
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; // 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)
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.
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.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()