Difference between revisions of "Programming in D for C++ Programmers"
JakobOvrum (talk | contribs) m (JakobOvrum moved page Coming From/C Plus Plus WIP article to Coming From/C Plus Plus: Taking out of draft status (still needs improvement, though!)) |
m (→See Also) |
||
(8 intermediate revisions by 3 users not shown) | |||
Line 34: | Line 34: | ||
hello, world | hello, world | ||
</pre> | </pre> | ||
+ | Executing scripts with rdmd will automatically compile the script and detected dependencies into | ||
+ | an executable stored in a temporary location then immediately execute it. The build is cached; | ||
+ | compilation only occurs when the script has been modified. | ||
== Initialization == | == Initialization == | ||
Line 109: | Line 112: | ||
== Checked memory safety and functional purity == | == Checked memory safety and functional purity == | ||
− | D supports opt-in, transitively-enforced memory safety: | + | D supports opt-in, transitively-enforced [https://en.wikipedia.org/wiki/Memory_safety memory safety]: |
<syntaxhighlight lang="D"> | <syntaxhighlight lang="D"> | ||
import std.stdio; | import std.stdio; | ||
Line 119: | Line 122: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | D also supports opt-in functional purity, giving semantic cues to both the programmer and the compiler. | + | D also supports opt-in [https://en.wikipedia.org/wiki/Purely_functional functional purity], |
− | Functional purity makes programs easier to reason about for programmers, and easier to optimize for compilers. | + | giving semantic cues to both the programmer and the compiler. Functional purity makes programs |
+ | easier to reason about for programmers, and easier to optimize for compilers. For example: | ||
+ | <syntaxhighlight lang="D"> | ||
+ | int square(int x) pure | ||
+ | { | ||
+ | return x * x; | ||
+ | } | ||
+ | |||
+ | void main() | ||
+ | { | ||
+ | import std.stdio; | ||
+ | |||
+ | for(int i = 0; i <= square(2); ++i) | ||
+ | writeln(i); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | In the above program, the compiler knows that <code>square(2)</code> only needs to be computed once, and can be | ||
+ | cached for future calls. | ||
== Concurrency == | == Concurrency == | ||
Line 127: | Line 147: | ||
import core.atomic; | import core.atomic; | ||
− | // Static variables are | + | // Static variables are in Thread Local Storage by default |
int tlsVar = 0; | int tlsVar = 0; | ||
Line 137: | Line 157: | ||
{ | { | ||
import std.stdio; | import std.stdio; | ||
− | writeln(tlsVar, " ", globalVar | + | |
+ | // globalVar is known to be shared, so it's loaded atomically | ||
+ | writeln(tlsVar, " ", globalVar, " ", immutableVar); | ||
} | } | ||
Line 145: | Line 167: | ||
tlsVar = 42; | tlsVar = 42; | ||
− | globalVar | + | globalVar = 42; // Known to be shared; stored atomically |
printVars(); // Output: 42 42 42 | printVars(); // Output: 42 42 42 | ||
Line 151: | Line 173: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | The second invocation of <code>printVars</code> is executed in a different thread from the main thread, | ||
+ | which means <code>tlsVar</code> refers to a separate variable from the one assigned to in <code>main</code>, | ||
+ | preventing bug-prone memory sharing. | ||
+ | |||
[http://dlang.org/phobos/std_concurrency.html std.concurrency] presents a message-passing API for threading, | [http://dlang.org/phobos/std_concurrency.html std.concurrency] presents a message-passing API for threading, | ||
and [http://dlang.org/phobos/std_parallelism.html std.parallelism] presents an API for easy parallelization. | and [http://dlang.org/phobos/std_parallelism.html std.parallelism] presents an API for easy parallelization. | ||
Line 159: | Line 185: | ||
<code>const</code> bridges mutable and immutable data into a common supertype, enabling functions to receive both | <code>const</code> bridges mutable and immutable data into a common supertype, enabling functions to receive both | ||
− | mutable and immutable data. | + | mutable and immutable data: |
+ | <syntaxhighlight lang="D"> | ||
+ | void print(const(char)[] text) // Can receive both mutable and immutable data | ||
+ | { | ||
+ | import std.stdio; | ||
+ | // text[0] = 'x'; // Error; cannot mutate data through const reference, as it could be immutable | ||
+ | writeln(text); | ||
+ | } | ||
+ | |||
+ | void main() | ||
+ | { | ||
+ | immutable(char[]) immut = "immutable"; // Guaranteed to never change | ||
+ | // immut[0] = 'x'; // Error; cannot mutate immutable data | ||
+ | print(immut); // Output: immutable | ||
+ | |||
+ | char[] mut = "xutable".dup; // String literals are immutable, so make a copy | ||
+ | mut[0] = 'm'; // OK, characters in mut are mutable | ||
+ | print(mut); // Output: mutable | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | Note that <code>string</code> is a shortcut alias of <code>immutable(char)[]</code> for convenience. | ||
== Metaprogramming == | == Metaprogramming == | ||
D templates are both more expressive and easier to read and write than C++ templates. static-if enables conditional branching | D templates are both more expressive and easier to read and write than C++ templates. static-if enables conditional branching | ||
in imperative style, eliminating the need for specialized dummy types. | in imperative style, eliminating the need for specialized dummy types. | ||
+ | |||
+ | The following example illustrates a simple function template that uses static-if for specialization: | ||
+ | <syntaxhighlight lang="D"> | ||
+ | // Function template | ||
+ | void cprint(T)(T value) | ||
+ | { | ||
+ | import core.stdc.stdio : printf; | ||
+ | import std.traits : isIntegral, isSigned, isSomeString, Unqual; | ||
+ | |||
+ | static if(is(Unqual!T == bool)) // Unqual handles cases of const, immutable or shared bool | ||
+ | printf(value? "true\n" : "false\n"); | ||
+ | else static if(isIntegral!T) | ||
+ | { | ||
+ | // Below initializer is evaluated at compile-time | ||
+ | static immutable format = "%" ~ (T.sizeof == 8? "l" : "") ~ (isSigned!T? "i" : "u") ~ "\n"; | ||
+ | printf(format.ptr, value); | ||
+ | } | ||
+ | else static if(is(T : const(char)[])) | ||
+ | printf("%.*s\n", value.length, value.ptr); | ||
+ | else | ||
+ | static assert(false, "T must be of boolean, integer or UTF-8 string type"); | ||
+ | } | ||
+ | |||
+ | void main() | ||
+ | { | ||
+ | cprint(true); // true | ||
+ | cprint(42); // 42 | ||
+ | cprint(24UL); // 24 | ||
+ | cprint("test"); // test | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | (The above snippet uses C's <code>printf</code> for illustrative purposes; D's <code>std.stdio.write[f]ln</code> can do all the above and more) | ||
== Ranges vs Iterators == | == Ranges vs Iterators == | ||
Line 211: | Line 289: | ||
=== Dangling Else === | === Dangling Else === | ||
D disallows [https://en.wikipedia.org/wiki/Dangling_else dangling else]: | D disallows [https://en.wikipedia.org/wiki/Dangling_else dangling else]: | ||
+ | |||
+ | ==See also== | ||
+ | * [[Building a mixed C++ and D project]] | ||
+ | |||
+ | ==External links== | ||
+ | * [http://dlang.org/cpptod.html Programming in D for C++ Programmers] | ||
+ | * [https://p0nce.github.io/d-idioms/#How-does-D-improve-on-C++17? How does D improve on C++ 17?] | ||
+ | * [https://dlang.org/articles/template-comparison.html Template Comparison - C++ versus D] | ||
+ | * [https://dlang.org/spec/cpp_interface.html Interfacing to C++] | ||
+ | |||
+ | [[Category:Languages versus D]] |
Latest revision as of 01:15, 14 December 2018
Contents
Overview
D is a modern multi-paradigm programming language that emphasizes both productivity and native efficiency. D does not compromise performance while making great strides in productivity and program correctness, which makes it a great choice for C++ programmers.
D also emphasizes modeling power: D allows for the design and implementation of high-level interfaces without compromising performance, allowing unparalleled code reuse in performance-sensitive projects.
D can interface natively with both C and C++ code while shedding the burden of backwards compatibility with C source code, which allows it to make the easy way the correct way.
Header Files
D has a comprehensive, no-nonsense module system that alleviates the need for header files. Symbols can be forward-referenced freely.
D still supports the equivalent of header files - D interface files - which can be used to interface with C or C++ code, or closed-source D code.
Compile Times
D is designed to be fast to compile. Despite having more comprehensive and intuitive metaprogramming features than C++, compilation times tend to be significantly shorter for equivalent programs.
The short compilation time also makes D suitable for writing scripts, executed with rdmd:
#!/usr/bin/env rdmd
void main()
{
writeln("hello, world");
}
$ chmod +x hello.d && ./hello.d hello, world
Executing scripts with rdmd will automatically compile the script and detected dependencies into an executable stored in a temporary location then immediately execute it. The build is cached; compilation only occurs when the script has been modified.
Initialization
In D, all variables are initialized by default:
int i;
writeln(i); // 0
double f;
writeln(f); // nan
int* p;
writeln(p); // null
Variables can be left explicitly uninitialized:
int i = void;
// i is not initialized; its value is undetermined
Thus the default is flipped in favor of safe, bug-free code.
Slicing problem
D's type system solves the slicing problem by separating user-defined types into types with inheritance, classes, and types without inheritance, structs.
Classes and interfaces are implicit reference types with support for class-based inheritance:
class A
{
abstract int getX();
}
class B : A
{
int x;
this(int x)
{
this.x = x;
}
override int getX()
{
return x;
}
}
void main()
{
import std.stdio;
A a = new B(42); // OK, `A` is a reference type!
writeln(a.getX()); // 42
}
Structs are value types, but still support subtyping:
struct A
{
int x;
}
struct B
{
A a;
alias a this; // Subtype A through field `a`
}
void main()
{
import std.stdio;
auto b = B(A(42));
writeln(b.x); // 42
}
Checked memory safety and functional purity
D supports opt-in, transitively-enforced memory safety:
import std.stdio;
void main() @safe // Verified by the compiler to be memory safe
{
writeln("hello, world");
}
D also supports opt-in functional purity, giving semantic cues to both the programmer and the compiler. Functional purity makes programs easier to reason about for programmers, and easier to optimize for compilers. For example:
int square(int x) pure
{
return x * x;
}
void main()
{
import std.stdio;
for(int i = 0; i <= square(2); ++i)
writeln(i);
}
In the above program, the compiler knows that square(2)
only needs to be computed once, and can be
cached for future calls.
Concurrency
D's memory model is designed to make shared memory explicit, greatly reducing the potential for concurrency bugs:
import core.atomic;
// Static variables are in Thread Local Storage by default
int tlsVar = 0;
// Static variables of shared or immutable types are global
shared(int) globalVar = 0;
immutable(int) immutableVar = 42;
void printVars()
{
import std.stdio;
// globalVar is known to be shared, so it's loaded atomically
writeln(tlsVar, " ", globalVar, " ", immutableVar);
}
void main()
{
import std.concurrency;
tlsVar = 42;
globalVar = 42; // Known to be shared; stored atomically
printVars(); // Output: 42 42 42
spawn(&printVars); // Output: 0 42 42
}
The second invocation of printVars
is executed in a different thread from the main thread,
which means tlsVar
refers to a separate variable from the one assigned to in main
,
preventing bug-prone memory sharing.
std.concurrency presents a message-passing API for threading, and std.parallelism presents an API for easy parallelization.
Immutability
D supports transitive immutable data, enforced by the type system. A variable of immutable type is guaranteed to never change after initialization.
const
bridges mutable and immutable data into a common supertype, enabling functions to receive both
mutable and immutable data:
void print(const(char)[] text) // Can receive both mutable and immutable data
{
import std.stdio;
// text[0] = 'x'; // Error; cannot mutate data through const reference, as it could be immutable
writeln(text);
}
void main()
{
immutable(char[]) immut = "immutable"; // Guaranteed to never change
// immut[0] = 'x'; // Error; cannot mutate immutable data
print(immut); // Output: immutable
char[] mut = "xutable".dup; // String literals are immutable, so make a copy
mut[0] = 'm'; // OK, characters in mut are mutable
print(mut); // Output: mutable
}
Note that string
is a shortcut alias of immutable(char)[]
for convenience.
Metaprogramming
D templates are both more expressive and easier to read and write than C++ templates. static-if enables conditional branching in imperative style, eliminating the need for specialized dummy types.
The following example illustrates a simple function template that uses static-if for specialization:
// Function template
void cprint(T)(T value)
{
import core.stdc.stdio : printf;
import std.traits : isIntegral, isSigned, isSomeString, Unqual;
static if(is(Unqual!T == bool)) // Unqual handles cases of const, immutable or shared bool
printf(value? "true\n" : "false\n");
else static if(isIntegral!T)
{
// Below initializer is evaluated at compile-time
static immutable format = "%" ~ (T.sizeof == 8? "l" : "") ~ (isSigned!T? "i" : "u") ~ "\n";
printf(format.ptr, value);
}
else static if(is(T : const(char)[]))
printf("%.*s\n", value.length, value.ptr);
else
static assert(false, "T must be of boolean, integer or UTF-8 string type");
}
void main()
{
cprint(true); // true
cprint(42); // 42
cprint(24UL); // 24
cprint("test"); // test
}
(The above snippet uses C's printf
for illustrative purposes; D's std.stdio.write[f]ln
can do all the above and more)
Ranges vs Iterators
D espouses ranges, an evolution of the iterator concept more conducive to component programming. The following program reads lines from standard input, puts them in an array, sorts them, and prints them to standard output in order:
import std.stdio;
import std.array;
import std.algorithm;
void main()
{
stdin
.byLine(KeepTerminator.yes)
.map!(a => a.idup)
.array
.sort
.copy(
stdout.lockingTextWriter());
}
Ali Çehreli's online book Programming in D has a chapter on ranges.
See also Andrei Alexandrescu's article On Iteration for a primer on the concept in the context of iterators and similar concepts in other languages.
Unicode
D and its standard library were designed with Unicode in mind, and support Unicode on a number of levels. D source files are UTF encoded, and the types string, wstring and dstring are UTF-8, UTF-16 and UTF-32 strings respectively. std.utf and std.uni are standard library modules presenting various UTF and Unicode algorithms. std.regex is Unicode-aware.
Iterative Improvements
By virtue of dropping backwards compatibility with C source code, D also fixes a number of smaller issues.
Function Hijacking
D's module system solves the issue of function hijacking.
Empty Statements
Empty statements using ;
are disallowed:
if(cond); // Error: use '{ }' for an empty statement, not a ';'
statement();
Dangling Else
D disallows dangling else: