1521 lines
No EOL
38 KiB
Markdown
1521 lines
No EOL
38 KiB
Markdown
# Hands-On DLang
|
||
|
||
* [Setup](#setup)
|
||
* [Installing DMD and DUB](#installing-dmd-and-dub)
|
||
* [OS X](#os-x)
|
||
* [Installing with Homebrew (recommended)](#installing-with-homebrew-recommended)
|
||
* [Installing locally using the install script](#installing-locally-using-the-install-script)
|
||
* [Installing using the installer](#installing-using-the-installer)
|
||
* [Windows](#windows)
|
||
* [Recommended editor setup](#recommended-editor-setup)
|
||
* [Installation of Visual Studio Code](#installation-of-visual-studio-code)
|
||
* [Extension setup](#extension-setup)
|
||
* [Project setup and compiling](#project-setup-and-compiling)
|
||
* [Without DUB](#without-dub)
|
||
* [With DUB](#with-dub)
|
||
* [Basics](#basics)
|
||
* [Hello World](#hello-world)
|
||
* [Imports and modules](#imports-and-modules)
|
||
* [Selective imports](#selective-imports)
|
||
* [Scoped imports](#scoped-imports)
|
||
* [Imports match files and directories](#imports-match-files-and-directories)
|
||
* [Basic Types](#basic-types)
|
||
* [Type conversion](#type-conversion)
|
||
* [Type properties](#type-properties)
|
||
* [Indexing](#indexing)
|
||
* [Variable declarations](#variable-declarations)
|
||
* [Mutability](#mutability)
|
||
* [`immutable`](#immutable)
|
||
* [`const`](#const)
|
||
* [Functions](#functions)
|
||
* [Return type deduction](#return-type-deduction)
|
||
* [Default arguments](#default-arguments)
|
||
* [Local functions](#local-functions)
|
||
* [Memory and pointers](#memory-and-pointers)
|
||
* [Memory safety](#memory-safety)
|
||
* [Structs](#structs)
|
||
* [Member functions](#member-functions)
|
||
* [`const` member functions](#const-member-functions)
|
||
* [`static` member functions](#static-member-functions)
|
||
* [Arrays](#arrays)
|
||
* [Static arrays](#static-arrays)
|
||
* [Dynamic arrays](#dynamic-arrays)
|
||
* [Array operations and properties](#array-operations-and-properties)
|
||
* [Slices](#slices)
|
||
* [Alias and `string`s](#alias-and-strings)
|
||
* [Control flow](#control-flow)
|
||
* [if…else](#ifelse)
|
||
* [switch…case](#switchcase)
|
||
* [Old fashioned loops](#old-fashioned-loops)
|
||
* [Breaking out of outer loops](#breaking-out-of-outer-loops)
|
||
* [`foreach` loops](#foreach-loops)
|
||
* [Element iteration](#element-iteration)
|
||
* [Access by reference](#access-by-reference)
|
||
* [Iterate `n` times](#iterate-n-times)
|
||
* [Iteration with index counter](#iteration-with-index-counter)
|
||
* [Ranges](#ranges)
|
||
* [Laziness](#laziness)
|
||
* [Copying ranges](#copying-ranges)
|
||
* [`RandomAccessRange`s](#randomaccessranges)
|
||
* [Lazy range algorithms](#lazy-range-algorithms)
|
||
* [Associative arrays](#associative-arrays)
|
||
* [Classes](#classes)
|
||
* [Inheritance](#inheritance)
|
||
* [Final and abstract member functions](#final-and-abstract-member-functions)
|
||
* [Checking for identity](#checking-for-identity)
|
||
* [Interfaces](#interfaces)
|
||
* [Templates](#templates)
|
||
* [Template functions](#template-functions)
|
||
* [Other templates](#other-templates)
|
||
* [Template value parameters](#template-value-parameters)
|
||
* [Other template parameters](#other-template-parameters)
|
||
* [Delegates](#delegates)
|
||
* [Functions as arguments](#functions-as-arguments)
|
||
* [Local functions with context](#local-functions-with-context)
|
||
* [Anonymous functions and lambdas](#anonymous-functions-and-lambdas)
|
||
* [Exceptions](#exceptions)
|
||
* [`nothrow`](#nothrow)
|
||
* [Gems](#gems)
|
||
* [Uniform function call syntax (UFCS)](#uniform-function-call-syntax-ufcs)
|
||
* [Scope guards](#scope-guards)
|
||
* [Range algorithms](#range-algorithms)
|
||
* [Unit testing](#unit-testing)
|
||
* [Example](#example)
|
||
* [Executing unit tests](#executing-unit-tests)
|
||
* [Code coverage](#code-coverage)
|
||
* [String mixins](#string-mixins)
|
||
* [Compile Time Function Evaluation (CTFE)](#compile-time-function-evaluation-ctfe)
|
||
* [Conditional compilation](#conditional-compilation)
|
||
* [`static if` & `is`](#static-if-&-is)
|
||
* [`static foreach`](#static-foreach)
|
||
* [Template constraints](#template-constraints)
|
||
|
||
|
||
|
||
## Setup
|
||
|
||
### Installing DMD and DUB
|
||
|
||
#### OS X
|
||
|
||
##### Installing with Homebrew (recommended)
|
||
|
||
```bash
|
||
brew install dmd
|
||
brew install dub
|
||
```
|
||
|
||
##### Installing locally using the install script
|
||
|
||
```bash
|
||
curl -fsS https://dlang.org/install.sh | bash -s dmd
|
||
echo "~/.dlang/dmd-2.080.0/activate" >> ~/.profile # Add dmd and dub to PATH on starting a bash shell
|
||
```
|
||
|
||
##### Installing using the installer
|
||
|
||
* Download http://downloads.dlang.org/releases/2.x/2.080.0/dmd.2.080.0.dmg.
|
||
* Open `dmd.2.080.0.dmg`
|
||
* Run `DMD2.pkg` (you might need to activate the “allow installing applications
|
||
from unverified developers” option in your security settings) and install with
|
||
the default settings.
|
||
|
||
#### Windows
|
||
|
||
* Download http://downloads.dlang.org/releases/2.x/2.080.0/dmd-2.080.0.exe.
|
||
* Run `dmd-2.080.0.exe` and install with the default settings (this will also
|
||
install Visual Studio if you do not have it installed yet).
|
||
|
||
### Recommended editor setup
|
||
|
||
Visual Studio Code is the recommended editor, because it has the best D
|
||
integration at the moment. If you want to use another editor or IDE, that is
|
||
perfectly fine. However, instructions will only be provided for Visual Studio
|
||
Code.
|
||
|
||
#### Installation of Visual Studio Code
|
||
|
||
Download and install Visual Studio Code from here:
|
||
https://code.visualstudio.com/. OS X users can also install it using Homebrew:
|
||
|
||
```bash
|
||
brew tap caskroom/cask
|
||
brew cask install visual-studio-code
|
||
```
|
||
|
||
#### Extension setup
|
||
|
||
* Open the Extension view in the sidebar:
|
||
|
||
| Operating system | Shortcut |
|
||
| ---------------- | --------- |
|
||
| OS X | ⌘ + ⇧ + X |
|
||
| Windows | ⌃ + ⇧ + X |
|
||
|
||
* Install the extension “D Programming Language (code-d)” (requires that git is
|
||
installed).
|
||
* Restart Visual Studio Code.
|
||
|
||
### Project setup and compiling
|
||
|
||
#### Without DUB
|
||
|
||
No special project setup is needed, you can order your source files any way you
|
||
like. You can compile them by invoking DMD:
|
||
|
||
```bash
|
||
dmd path/to/sourcefile1.d path/to/sourcefile2.d …
|
||
```
|
||
|
||
This generates a binary with the same name as the first sourcefile provided.
|
||
|
||
#### With DUB
|
||
|
||
To init a DUB project, just run
|
||
|
||
```bash
|
||
dub init
|
||
```
|
||
|
||
inside your projects root folder. This runs an interactive dialog which allows
|
||
you to specify some settings for the project (the project name etc.). Depending
|
||
on your choice, these settings are saved in a file called either `dub.json` or
|
||
`dub.sdl`.
|
||
|
||
By default, the source files will be contained in the `source` subdirectory.
|
||
|
||
To compile the project, simply run
|
||
|
||
```bash
|
||
dub build
|
||
```
|
||
|
||
in the project’s root directory. This generates a binary with the same name as
|
||
the configured project name. It is also possible to compile and run in one step,
|
||
just run
|
||
|
||
```bash
|
||
dub run
|
||
```
|
||
|
||
in the project’s root directory.
|
||
|
||
## Basics
|
||
|
||
### Hello World
|
||
|
||
```D
|
||
import std.stdio;
|
||
|
||
void main() {
|
||
writeln("Hello World");
|
||
}
|
||
```
|
||
|
||
### Imports and modules
|
||
|
||
D has the concept of _modules_ and _packages_.
|
||
By importing a certain module with the `import` statement, all public symbols
|
||
from module become available. The standard library, called Phobos, is located
|
||
in the `std` package. E.g. in order to import the `file` module from Phobos, you
|
||
would write:
|
||
|
||
```D
|
||
import std.file;
|
||
```
|
||
|
||
#### Selective imports
|
||
|
||
It is possible (and often good style) to import symbols selectively from a
|
||
module:
|
||
|
||
```D
|
||
import std.stdio: writeln, writefln;
|
||
```
|
||
|
||
#### Scoped imports
|
||
|
||
It is not necessary to place imports at the beginning of a file.
|
||
They can be located anywhere in the code.
|
||
If they appear inside a certain scope (delimited by braces), the imported
|
||
symbols are only available inside that scope. Here is an alternative version of
|
||
the hello world program:
|
||
|
||
```D
|
||
void main() {
|
||
import std.stdio: writeln;
|
||
writeln("Hello World");
|
||
}
|
||
/* writeln is not available outside of the main function */
|
||
```
|
||
|
||
#### Imports match files and directories
|
||
|
||
The module system is entirely based on files.
|
||
E.g. `my.thing` refers to a file `thing.d` in the folder `my/`.
|
||
|
||
### Basic Types
|
||
|
||
D has the following basic types:
|
||
|
||
| Data types | Size |
|
||
| ------------------------------- | ------------------------------------------------------------ |
|
||
| `bool`, `byte`, `ubyte`, `char` | 8-bit |
|
||
| `short`, `ushort`, `wchar` | 16-bit |
|
||
| `int`, `uint`, `dchar`, `float` | 32-bit |
|
||
| `long`, `ulong`, `double` | 64-bit |
|
||
| `real` | >= 64-bit (generally 64-bit, but 80-bit on Intel x86 32-bit) |
|
||
|
||
`char` represents UTF-8 characters, `wchar`represents UTF-16 characters, and
|
||
`dchar` represents UTF-32 characters.
|
||
|
||
#### Type conversion
|
||
|
||
For integer types, automatic type conversion is only allowed if no precision is
|
||
lost (e.g. `int` to `long`). All conversion between floating point types are
|
||
allowed (e.g. `double` to `float`).
|
||
|
||
Manual type conversion is achieved with the `cast` expression:
|
||
|
||
```D
|
||
long a = 1;
|
||
int b = cast(int) a;
|
||
```
|
||
|
||
#### Type properties
|
||
|
||
All types have a property `.init` to which variables of that type are
|
||
initialized, if they are not initialized explicitly. For integer types, this is
|
||
`0` and for floating point types it is `nan`.
|
||
|
||
Every type also has a `.stringof` property which yields its name as a string.
|
||
|
||
Integer types have some more properties:
|
||
|
||
| Property | Description |
|
||
| -------- | ----------------------------------- |
|
||
| `.max` | The maximum value the type can hold |
|
||
| `.min` | The minimum value the type can hold |
|
||
|
||
And so do floating point types:
|
||
|
||
| Property | Description |
|
||
| ------------- | ----------------------------------------------------------- |
|
||
| `.max` | The maximum value the type can hold |
|
||
| `.min_normal` | The smallest representable normalized value that is not `0` |
|
||
| `.nan` | NaN value |
|
||
| `.infinity` | Infinity value |
|
||
| `.dig` | number of decimal digits of precision |
|
||
| `.mant_dig` | number of bits in mantissa |
|
||
| … | |
|
||
|
||
#### Indexing
|
||
|
||
For indexing, usually the alias type `size_t` is used, which is large enough to
|
||
represent an offset into all addressable memory.
|
||
|
||
### Variable declarations
|
||
|
||
Variables are declared by writing the type followed by the variable name:
|
||
|
||
```D
|
||
int myVar;
|
||
```
|
||
|
||
They can also be explicitly initialized:
|
||
|
||
```D
|
||
int myVar = 42;
|
||
```
|
||
|
||
It is also possible to declare several variables at once:
|
||
|
||
```D
|
||
int myVar, someOtherVar;
|
||
```
|
||
|
||
D has automatic type deduction, so when explicitly initializing a variable, it
|
||
is not necessary to mention the type. Instead we can use the `auto` keyword:
|
||
|
||
```D
|
||
auto myVar = 42;
|
||
```
|
||
|
||
Here is a combination of the above notations:
|
||
|
||
```D
|
||
auto myInt = 42, myFloat = 4.2f;
|
||
```
|
||
|
||
### Mutability
|
||
|
||
Objects in D are mutable by default, but is possible to change this using
|
||
type qualifiers:
|
||
|
||
#### `immutable`
|
||
|
||
An object declared as `immutable` is enforced by the compiler to never change
|
||
its value.
|
||
|
||
```D
|
||
immutable int a;
|
||
a = 5; // error
|
||
```
|
||
|
||
`immutable` objects are implicitly shared across threads, because the can never
|
||
change their value and thus race conditions are impossible.
|
||
|
||
#### `const`
|
||
|
||
`const` objects also can not be modified, but this is enforced only in the
|
||
current scope. This means, that the object could be modified from a different
|
||
scope. Both mutable and `immutable` objects implicitly convert to `const`
|
||
objects:
|
||
|
||
```D
|
||
void foo(const char[] s)
|
||
{
|
||
// Do something with s
|
||
}
|
||
// Both calls are valid, thanks to const
|
||
foo("abcd"); // a string is an immutable array of char
|
||
foo("abcd".dup); // dup creates a mutable copy
|
||
```
|
||
|
||
Both `immutable` and `const` are transitive, i.e. the apply recursively to all
|
||
subcomponents of a type they are applied to.
|
||
|
||
### Functions
|
||
|
||
The basic syntax for functions is very similar to C:
|
||
|
||
```D
|
||
int add(int lhs, int rhs) {
|
||
return lhs + rhs;
|
||
}
|
||
```
|
||
|
||
#### Return type deduction
|
||
|
||
A functions return type can be defined to be `auto`.
|
||
In this case, the return type will be inferred.
|
||
Multiple return statements are possible, but must return compatible types.
|
||
|
||
```D
|
||
auto add(int lhs, int rhs) { // returns `int`
|
||
return lhs + rhs;
|
||
}
|
||
|
||
auto lessOrEqual(int lhs, int rhs) { // returns `double`
|
||
if (lhs <= rhs)
|
||
return 0;
|
||
else
|
||
return 1.0;
|
||
}
|
||
```
|
||
|
||
#### Default arguments
|
||
|
||
Those also work the same as in C and other languages:
|
||
|
||
```D
|
||
void plot(string msg, string color = "red") {
|
||
/* ... */
|
||
}
|
||
plot("D rocks");
|
||
plot("D rocks", "blue");
|
||
```
|
||
|
||
#### Local functions
|
||
|
||
It is possible to define functions locally (even inside other functions).
|
||
Those functions are not visible outside their parents scope.
|
||
|
||
```D
|
||
void fun() {
|
||
int local = 10;
|
||
int fun_secret() {
|
||
local++; // that's legal
|
||
}
|
||
/* … */
|
||
}
|
||
static assert(!__traits(compiles, fun_secret())); // fun_secret is not visible here
|
||
```
|
||
|
||
### Memory and pointers
|
||
|
||
D uses a garbage collector by default, but is also possible to do manual memory
|
||
management if needed.
|
||
|
||
D provides pointer types `T*` like in C:
|
||
|
||
```D
|
||
int a;
|
||
int* b = &a; // b contains address of a
|
||
auto c = &a; // c is int* and contains address of a
|
||
```
|
||
|
||
To allocate a new memory block on the garbage collected heap, use the `new`
|
||
operator:
|
||
|
||
```D
|
||
int* a = new int;
|
||
```
|
||
|
||
#### Memory safety
|
||
|
||
In general, pointer arithmetic like in C is allowed. This results in the usual
|
||
safety issues. To counter this, D defines 3 safety levels for functions:
|
||
`@safe`, `@trusted`, and `@system`. The default is `@system`, which gives no
|
||
safety guarantees. Functions annotated with `@safe` are only allowed to call
|
||
other `@safe` and `@trusted` functions and it is not possible to do pointer
|
||
arithmetic in them:
|
||
|
||
```D
|
||
void main() @safe {
|
||
int a = 5;
|
||
int* p = &a;
|
||
int* c = p + 5; // error
|
||
}
|
||
```
|
||
|
||
`@trusted` functions are functions that are manually verified to provide an
|
||
`@safe` interface. They create a bridge between `@safe` code and dirty low-level
|
||
code. Only use them very carefully!
|
||
|
||
### Structs
|
||
|
||
One way to create custom data types in D is with `struct`s:
|
||
|
||
```D
|
||
struct Person {
|
||
int age;
|
||
int height;
|
||
}
|
||
```
|
||
|
||
Unless created with the `new` operator, `sturct`s are always constructed on the
|
||
stack and copied _by value_ in assignments and as parameters to function calls.
|
||
|
||
```D
|
||
auto p = Person(30, 180);
|
||
auto t = p; // copy
|
||
```
|
||
|
||
You can also define a custom constructor:
|
||
|
||
```D
|
||
struct Person {
|
||
int age;
|
||
int height;
|
||
this(int age, int height) {
|
||
this.age = age;
|
||
this.height = height;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Member functions
|
||
|
||
`struct`s can have member functions. By default, they are `public` and
|
||
accessible from outside. By marking them `private`, you can limit access to
|
||
functions in the same module (different from C++ / Java etc.!):
|
||
|
||
```D
|
||
struct Person {
|
||
void doStuff() {
|
||
/* … */
|
||
}
|
||
private void privateStuff() {
|
||
/* … */
|
||
}
|
||
}
|
||
auto p = Person();
|
||
p.doStuff(); // call method doStuff
|
||
p.privateStuff(); // forbidden
|
||
```
|
||
|
||
#### `const` member functions
|
||
|
||
Member functions declared const can not modify any members. They can be called
|
||
on `immutable` and `const` objects.
|
||
|
||
#### `static` member functions
|
||
|
||
They work basically the same as in C etc.
|
||
|
||
### Arrays
|
||
|
||
D has two types of arrays, static arrays and dynamic arrays. Both of them are
|
||
bounds checked unless this feature is explicitly switched of with the compiler
|
||
flag `--boundscheck=off`.
|
||
|
||
#### Static arrays
|
||
|
||
Static arrays are stored on he stack or in static memory, depending on where
|
||
they are defined. They have a fixed, compile time known length. The length is
|
||
part of the type:
|
||
|
||
```D
|
||
int[8] arr;
|
||
```
|
||
|
||
#### Dynamic arrays
|
||
|
||
Dynamic arrays are stored on the heap and have a variable length, which can
|
||
change during runtime. A dynamic array is created with the new expression:
|
||
|
||
```D
|
||
auto size = 8;
|
||
int[] arr = new int[size];
|
||
```
|
||
|
||
The type `int[]` is called a _slice_ of `int`. Slices will be explained in more
|
||
detail in the next section.
|
||
|
||
Creating multidimensional arrays is also easy:
|
||
|
||
```D
|
||
auto matrix = new int[3][3];
|
||
```
|
||
|
||
#### Array operations and properties
|
||
|
||
Array concatenation is done with the `~` operator, which creates a new dynamic
|
||
array.
|
||
|
||
Mathematical operations can be applied to whole arrays using a syntax like
|
||
`c[] = a[] + b[]`, for example. This adds all elements of `a` and `b` so that
|
||
`c[0] = a[0] + b[0]`, `c[1] = a[1] + b[1]`, etc. It is also possible to perform
|
||
operations on a whole array with a single value:
|
||
|
||
```D
|
||
a[] *= 2; // multiple all elements by 2
|
||
a[] %= 26; // calculate the modulo by 26 for all a's
|
||
```
|
||
|
||
These operations can be optimized by the compiler using _SIMD_ instructions.
|
||
|
||
Both static and dynamic arrays provide the property `.length`, which is
|
||
read-only for static arrays, but can be used in the case of dynamic arrays to
|
||
change its size dynamically. The property .dup creates a copy of the array.
|
||
|
||
When indexing an array through the `arr[idx]` syntax, a special `$` symbol
|
||
denotes an array's length. For example, `arr[$ - 1]` references the last element
|
||
and is a short form for `arr[arr.length - 1]`.
|
||
|
||
### Slices
|
||
|
||
Slices are object of the type `T[]`for any given type `T. Slices provide a
|
||
_view_ to a subset of an array.
|
||
|
||
A slice consists basically of two members:
|
||
|
||
```D
|
||
T* ptr;
|
||
size_t length;
|
||
```
|
||
|
||
As we have already seen in the previous section, we can get slices by
|
||
allocating a new dynamic array:
|
||
|
||
```D
|
||
auto arr = new int[5];
|
||
assert(arr.length == 5)
|
||
```
|
||
|
||
The slice does not own the memory, it is managed by the garbage collector. The
|
||
slice is just a view on the memory.
|
||
|
||
You can also get slices to already existing memory:
|
||
|
||
```D
|
||
auto arr = new int[5];
|
||
auto newArr = arr;
|
||
auto smallerViewArr = arr[1 .. 4]; // index 4 is not included
|
||
assert(smallerViewArr.length == 3);
|
||
assert(newArr.length == 5);
|
||
smallerViewArr[0] = 10;
|
||
assert(newArr[1] == 10 && arr[1] == 10);
|
||
```
|
||
|
||
Again, it is important to keep in mind, that this is only a view to memory. No
|
||
memory is copied.
|
||
|
||
### Alias and `string`s
|
||
|
||
By using the `alias` statement , we can create new “names” for existing types:
|
||
|
||
```D
|
||
alias string = immutable(char)[];
|
||
```
|
||
|
||
This works very similar to `typedef` from C / C++.
|
||
|
||
The above definition of `string` is actually the definition that is used by D.
|
||
This means that `string`s are just mutable slices of `immutable` `char`s.
|
||
|
||
### Control flow
|
||
|
||
#### if…else
|
||
|
||
Very similar to how it is defined in other languages:
|
||
|
||
```D
|
||
if (a == 5) {
|
||
writeln("Condition is met");
|
||
} else if (a > 10) {
|
||
writeln("Another condition is met");
|
||
} else {
|
||
writeln("Nothing is met!");
|
||
}
|
||
```
|
||
|
||
#### switch…case
|
||
|
||
Also very similar to how it is defined in other languages, but for it works for
|
||
integer types, bools and strings (which will be covered later).
|
||
|
||
```D
|
||
string myString;
|
||
/* … */
|
||
switch(myString) {
|
||
case "foo":
|
||
writeln(`Cool, myString was "foo"`);
|
||
break;
|
||
default:
|
||
writeln("Meh, myString was something boring");
|
||
break;
|
||
}
|
||
```
|
||
|
||
For integer types, it is also possible to define ranges:
|
||
|
||
```D
|
||
int c = 5;
|
||
switch(c) {
|
||
case 0: .. case 9:
|
||
writeln(c, " is within 0-9");
|
||
break; // necessary!
|
||
case 10:
|
||
writeln("A Ten!");
|
||
break;
|
||
default: // if nothing else matches
|
||
writeln("Nothing");
|
||
break;
|
||
}
|
||
```
|
||
|
||
#### Old fashioned loops
|
||
|
||
`while`-, `do`…`while`- and classical `for`-loops all work the same as in C++ /
|
||
Java etc.
|
||
|
||
##### Breaking out of outer loops
|
||
|
||
As usual, you can break out of a loop immediately by using the `break` keyword.
|
||
Additionally, you can also break out of outer loops by using labels:
|
||
|
||
```D
|
||
outer:
|
||
for (int i = 0; i < 10; ++i) {
|
||
for (int j = 0; j < 5; ++j) {
|
||
/* … */
|
||
break outer; // breaks out of the outer loop
|
||
}
|
||
}
|
||
```
|
||
|
||
#### `foreach` loops
|
||
|
||
D has a `foreach` loops which allows for much better readable iterations.
|
||
|
||
##### Element iteration
|
||
|
||
We can easily iterate over slices using `foreach`:
|
||
|
||
```D
|
||
auto arr = new int[5];
|
||
foreach (e; arr) {
|
||
writeln(e);
|
||
}
|
||
```
|
||
|
||
##### Access by reference
|
||
|
||
By default the elements are copied during the iteration. If we want _in-place_
|
||
modification, we can use the `ref` qualifier:
|
||
|
||
```D
|
||
auto arr = new int[5];
|
||
foreach (ref e; arr) {
|
||
e = 5;
|
||
}
|
||
```
|
||
|
||
##### Iterate `n` times
|
||
|
||
It is easy to write iterations, which should be executed `n` times by using the
|
||
`..` syntax:
|
||
|
||
```D
|
||
foreach (i; 0 .. 3) {
|
||
writeln(i);
|
||
}
|
||
// prints 0 1 2
|
||
```
|
||
|
||
##### Iteration with index counter
|
||
|
||
For slices, it's also possible to access a separate index variable:
|
||
|
||
```D
|
||
foreach (i, e; [4, 5, 6]) {
|
||
writeln(i, ":", e);
|
||
}
|
||
// prints 0:4 1:5 2:6
|
||
```
|
||
|
||
### Ranges
|
||
|
||
Ranges are a very important concept for iteration in D. We can use `foreach`
|
||
loops, to iterate over ranges:
|
||
|
||
```D
|
||
foreach (element; range) {
|
||
// Loop body
|
||
}
|
||
```
|
||
|
||
If we use `foreach` with a range, this gets lowered to the compiler to something
|
||
similar to this:
|
||
|
||
```D
|
||
for (auto __rangeCopy = range; !__rangeCopy.empty; __rangeCopy.popFront()) {
|
||
auto element = __rangeCopy.front;
|
||
// Loop body...
|
||
}
|
||
```
|
||
|
||
This leads us to what ranges (or more specific `InputRange`s) actually are:
|
||
Anything, that implements the member functions needed by the above lowering:
|
||
|
||
```D
|
||
interface InputRange(E) {
|
||
bool empty() @property;
|
||
E front() @property;
|
||
void popFront();
|
||
}
|
||
```
|
||
|
||
However, ranges do not need to _implement_ such an interface in terms of
|
||
inheritance, they just have to provide the above member functions.
|
||
Typically, ranges are implemented as `struct`s (because most of the time, ranges
|
||
should be value types), but is also possible to implement them as `class`es,
|
||
which will be introduced later.
|
||
|
||
#### Laziness
|
||
|
||
Ranges are _lazy_. They won't be evaluated until requested. Hence, a range from
|
||
an infinite range can be taken:
|
||
|
||
```D
|
||
42.repeat.take(3).writeln; // [42, 42, 42]
|
||
```
|
||
|
||
#### Copying ranges
|
||
|
||
Copying a range by just using the assignment operator might not have the desired
|
||
effect, because iterating over a range can be destructive (i.e. when the range
|
||
holds internal pointers and a deep copy would be necessary). “copyable” ranges
|
||
are called `ForwardRange`s. They need to implement a `.save` method which
|
||
returns a copy of the range:
|
||
|
||
```D
|
||
interface ForwardRange(E) : InputRange!E
|
||
{
|
||
typeof(this) save();
|
||
}
|
||
```
|
||
|
||
#### `RandomAccessRange`s
|
||
|
||
A `RandomAccessRange` is a `ForwardRange` which has a know `length` for which
|
||
each element can be access directly:
|
||
|
||
```D
|
||
interface RandomAccessRange(E) : ForwardRange!E
|
||
{
|
||
E opIndex(size_t i); // can access elements using range[i] syntax
|
||
size_t length() @property;
|
||
}
|
||
```
|
||
|
||
Slices are the most prominent example of `RandomAccessRange`s in
|
||
|
||
#### Lazy range algorithms
|
||
|
||
The D standard library provides a huge arsenal of lazy range algorithm
|
||
functions. Most of them can be found in in the `std.range` and `std.algorithm`
|
||
packages.
|
||
|
||
### Associative arrays
|
||
|
||
D has builtin hashmaps, which are called _associative arrays_:
|
||
|
||
```D
|
||
int[string] map; // keys of type string, values of type int
|
||
map["key1"] = 10; // insertion or modification, if the key already exists
|
||
if ("key1" in map) { // checking if a key is in an associative array
|
||
writeln("key1 is in map");
|
||
}
|
||
assert(map.length == 1); // associative arrays provide a .length property
|
||
map.remove("key1"); // remove a key from an associative array
|
||
```
|
||
|
||
### Classes
|
||
|
||
D's `class`es are very similar to Java's `class`es.
|
||
|
||
Any `class` type implicitly inherits from `Object`.
|
||
|
||
```D
|
||
class Foo { } // implicitly inherits from Object
|
||
class Bar : Foo { } // Bar inherits from Foo
|
||
```
|
||
|
||
`class`es are usually instantiated on the GC heap using `new`:
|
||
|
||
```D
|
||
auto bar = new Bar;
|
||
```
|
||
|
||
In contrast to `struct`s, `class`es are reference types:
|
||
|
||
```D
|
||
Bar bar = foo; // bar points to foo
|
||
```
|
||
|
||
#### Inheritance
|
||
|
||
If a member function of a base class if overwritten, the `override` keyword must
|
||
be used:
|
||
|
||
```D
|
||
class Bar: Foo {
|
||
override void functionFromFoo() {}
|
||
}
|
||
```
|
||
|
||
Classes can only inherit from a single class
|
||
|
||
#### Final and abstract member functions
|
||
|
||
* A function can be marked `final` in a base class to disallow overriding it.
|
||
* A function can be declared as `abstract` to force derived classes to override
|
||
it.
|
||
* A whole class can be declared as `abstract` to make sure that it isn't
|
||
instantiated.
|
||
* `super(…)` can be used to explicitly call the base constructor.
|
||
|
||
#### Checking for identity
|
||
|
||
The `==` operator compares the content of two class objects. Checking for
|
||
identity is done using the `is` operator. Comparing to `null` is only possible
|
||
with this operator:
|
||
|
||
```D
|
||
MyClass c;
|
||
if (c == null) // error
|
||
/* … */
|
||
if (c is null) // ok
|
||
/* … */
|
||
```
|
||
|
||
### Interfaces
|
||
|
||
`interface`s work basically the same as in Java:
|
||
|
||
```D
|
||
interface Animal {
|
||
void makeNoise();
|
||
}
|
||
|
||
class Dog : Animal {
|
||
override void makeNoise() {
|
||
writeln('woof!');
|
||
}
|
||
}
|
||
|
||
auto dog = new Dog;
|
||
Animal animal = dog; // implicit cast to interface
|
||
animal.makeNoise();
|
||
```
|
||
|
||
### Templates
|
||
|
||
#### Template functions
|
||
|
||
Template functions in D are very similar to C++ template functions:
|
||
|
||
```D
|
||
auto add(T)(T lhs, T rhs) {
|
||
return lhs + rhs;
|
||
}
|
||
|
||
add!int(5,10);
|
||
add!float(5.0f, 10.0f);
|
||
add!Animal(dog, cat); // error, Animal does not implement +
|
||
```
|
||
|
||
In the explicit template notation, the `add` template function would look like
|
||
this:
|
||
|
||
```D
|
||
template add(T) {
|
||
auto add(T lhs, T rhs) {
|
||
return lhs + rhs;
|
||
}
|
||
}
|
||
```
|
||
|
||
D also allows implicit function template instantiation:
|
||
|
||
```D
|
||
add(5, 10); // same as add!int(5,10)
|
||
```
|
||
|
||
Templates can have more than one template parameter:
|
||
|
||
```D
|
||
void print(S, T)(S x, T y) {
|
||
writeln(x);
|
||
writeln(y);
|
||
}
|
||
|
||
print!(int, string)(42, "is the best number");
|
||
```
|
||
|
||
#### Other templates
|
||
|
||
Of course, `struct`s, `class`es and `interface`s can also be templates:
|
||
|
||
```D
|
||
struct S(T) {
|
||
/* … */
|
||
}
|
||
|
||
auto s = S!int;
|
||
```
|
||
|
||
It is also possible to have variable templates:
|
||
|
||
```D
|
||
ubyte[T.sizeof * 8] buffer8(T) = 42;
|
||
|
||
auto myBuffer = buffer!(int, 8); // create a buffer with space for 8 ints and initialize its elements to 42
|
||
static assert(is(typeof(myBuffer) == ubyte[32]));
|
||
assert(myBuffer[0] == 42);
|
||
```
|
||
|
||
This gets lowered to the following explicit template syntax:
|
||
|
||
```D
|
||
template buffer8(T) {
|
||
ubyte[T.sizeof * 8] buffer8 = 42;
|
||
}
|
||
```
|
||
|
||
#### Template value parameters
|
||
|
||
So far we have only seen templateF type parameters. It is also possible to have
|
||
template value parameters:
|
||
|
||
```D
|
||
struct Animal(string noise) {
|
||
void makeNoise() {
|
||
writeln(noise);
|
||
}
|
||
}
|
||
|
||
auto dog = Animal!"woof!"();
|
||
dog.makeNoise(); // woof!
|
||
```
|
||
|
||
#### Other template parameters
|
||
|
||
There are some other types of template parameters: template alias parameters
|
||
and template sequence parameters. Template alias parameters can be any D symbol
|
||
(which means basically anything except for basic types):
|
||
|
||
```D
|
||
void print(alias var)() {
|
||
writeln(var);
|
||
}
|
||
|
||
struct S {}
|
||
auto x = 42;
|
||
print!x();
|
||
print!4.2f();
|
||
print!"foo"();
|
||
print!S(); // error, would be actually legal, but cant pass types to writeln
|
||
```
|
||
|
||
Template sequence parameters take a variadic number of template arguments:
|
||
|
||
```D
|
||
void print(Args...)(Args args) {
|
||
foreach (arg; args) { // yes, we can iterate over them (loop unrolling)
|
||
writeln(arg);
|
||
}
|
||
}
|
||
|
||
print(42, 3.5f, "foo");
|
||
print(); // also valid, prints nothing
|
||
```
|
||
|
||
### Delegates
|
||
|
||
#### Functions as arguments
|
||
|
||
Global functions can be referenced using the `function` type:
|
||
|
||
```D
|
||
void doSomething(int function(int, int) doer) {
|
||
// call passed function
|
||
doer(5,5);
|
||
}
|
||
|
||
auto add(int a, int b) {
|
||
return a + b;
|
||
}
|
||
|
||
doSomething(&add);
|
||
```
|
||
|
||
#### Local functions with context
|
||
|
||
To reference member functions or local functions, `delegate`s have to be used.
|
||
You can create _closures_ with this:
|
||
|
||
```D
|
||
auto foo() {
|
||
int x = 42;
|
||
auto local() {
|
||
return x;
|
||
}
|
||
return &local;
|
||
}
|
||
|
||
auto f = foo();
|
||
static assert(is(typeof(f) == int delegate() pure nothrow @nogc @safe));
|
||
writeln(f()); // 42
|
||
```
|
||
|
||
#### Anonymous functions and lambdas
|
||
|
||
You can write anonymous functions and lambdas like this:
|
||
|
||
```D
|
||
auto add = (int lhs, int rhs) {
|
||
return lhs + rhs;
|
||
};
|
||
|
||
auto lambdaAdd = (int lhs, int rhs) => lhs + rhs;
|
||
```
|
||
|
||
These are often passed as template arguments in the functional parts of Phobos:
|
||
|
||
```D
|
||
[1, 2, 3].reduce!((a, b) => a + b)();
|
||
```
|
||
|
||
### Exceptions
|
||
|
||
Exceptions in D are very similar to Exceptions in Java:
|
||
|
||
```D
|
||
class MyException : Exception {
|
||
this(string msg) {
|
||
super("MyException was thrown because of " ~ msg);
|
||
}
|
||
}
|
||
|
||
try {
|
||
throw new MyException("no reason");
|
||
}
|
||
catch (FileException e) { // not caught here
|
||
/* … */
|
||
}
|
||
catch (MyException e) { // but here
|
||
/* … */
|
||
}
|
||
finally { // executed regardless of whether an exception was thrown or not
|
||
/* … */
|
||
}
|
||
```
|
||
|
||
#### `nothrow`
|
||
|
||
The compiler can enforce that a function can not throw an exception. You can
|
||
achieve this by annotating a function with `nothrow`:
|
||
|
||
```D
|
||
import std.exception: enforce; // convenience function which throws if `false` is passed
|
||
|
||
int divide(int a, int b) {
|
||
enforce(b != 0, "Can't divide by 0!");
|
||
return a / b;
|
||
}
|
||
|
||
int divide4By2() nothrow {
|
||
return divide(4, 2); // error, divide my throw
|
||
}
|
||
```
|
||
|
||
## Gems
|
||
|
||
### Uniform function call syntax (UFCS)
|
||
|
||
_UFCS_ is a simple and yet very powerful feature of D. It basically allows
|
||
that any call to a free function `fun(a)` can be written as member function
|
||
call `a.fun()`. If the compiler sees `a.fun()` and the type of `a` does not
|
||
have a member function `fun()`, it tries to find a global function whose
|
||
first parameter matches the type of `a`.
|
||
|
||
This makes complex chains of function calls much more readably. Instead of
|
||
writing
|
||
|
||
```D
|
||
foo(bar(a));
|
||
```
|
||
|
||
one can write
|
||
|
||
```D
|
||
a.bar().foo();
|
||
```
|
||
|
||
For functions that take no arguments, it is not necessary to use parenthesis,
|
||
so any function like that can be used like a property:
|
||
|
||
```D
|
||
import std.uni: toLower;
|
||
auto a = "Cool Stuff".toLower;
|
||
assert(a == "cool stuff");
|
||
```
|
||
|
||
UFCS is especially important when dealing with ranges where several algorithms
|
||
can be put together to perform complex operations, still allowing to write clear and manageable code.
|
||
|
||
```D
|
||
import std.algorithm : group;
|
||
import std.range : chain, retro, front, retro;
|
||
[1, 2].chain([3, 4]).retro; // 4, 3, 2, 1
|
||
[1, 1, 2, 2, 2].group.dropOne.front; // tuple(2, 3u)
|
||
```
|
||
|
||
### Scope guards
|
||
|
||
Scope guards allow executing statements at certain conditions if the current
|
||
block is left:
|
||
|
||
* `scope(exit)` will always call the statements.
|
||
* `scope(success)` statements are called when no exceptions have been thrown.
|
||
* `scope(failure)` denotes statements that will be called when an exception
|
||
has been thrown before the block's end.
|
||
|
||
Using scope guards makes code much cleaner and allows to place resource
|
||
allocation and clean up code next to each other. They also improve safety
|
||
because they make sure certain cleanup code is always called independent of
|
||
which paths are actually taken at runtime.
|
||
|
||
Scope guards are called in the reverse order they are defined.
|
||
|
||
```D
|
||
void foo() {
|
||
import core.stdc.stdlib : free, malloc;
|
||
int* p = cast(int*) malloc(int.sizeof);
|
||
scope(exit) free(p);
|
||
/* Do some stuff, which potentially might throw, which does not matter,
|
||
p is freed anyways when leaving the scope */
|
||
}
|
||
```
|
||
|
||
### Range algorithms
|
||
|
||
The modules `std.range` and `std.algorithm` contain many functions which can be
|
||
composed to express complex operations in a readable way. They are based on
|
||
ranges and will work on your data types, if they implement the range interface.
|
||
|
||
```D
|
||
import std.algorithm : canFind, map,
|
||
filter, sort, uniq, joiner, chunkBy, splitter;
|
||
import std.array : array, empty;
|
||
import std.range : zip;
|
||
import std.stdio : writeln;
|
||
import std.string : format;
|
||
|
||
void main() {
|
||
string text = `This tour will give you an
|
||
overview of this powerful and expressive systems
|
||
programming language which compiles directly
|
||
to efficient, *native* machine code.`;
|
||
|
||
// splitting predicate
|
||
alias pred = c => canFind(" ,.\n", c);
|
||
// as a good algorithm it just works lazily without allocating memory!
|
||
auto words = text.splitter!pred
|
||
.filter!(a => !a.empty);
|
||
|
||
auto wordCharCounts = words
|
||
.map!"a.count";
|
||
|
||
// Output the character count per word in a nice way beginning with least chars!
|
||
zip(wordCharCounts, words)
|
||
// convert to array for sorting
|
||
.array()
|
||
.sort()
|
||
// we don't need duplication, right?
|
||
.uniq()
|
||
// put all in one row that have the same char count.
|
||
// chunkBy helps us here by generating a range of ranges
|
||
// that are chunked by the length
|
||
.chunkBy!(a => a[0])
|
||
// those elments will be joined
|
||
// on one line
|
||
.map!(chunk => format("%d -> %s",
|
||
chunk[0],
|
||
// just the words
|
||
chunk[1]
|
||
.map!(a => a[1])
|
||
.joiner(", ")))
|
||
// joiner joins, but lazily!
|
||
// and now the lines with the line feed
|
||
.joiner("\n")
|
||
// pipe to stdout
|
||
.writeln();
|
||
}
|
||
```
|
||
|
||
### Unit testing
|
||
|
||
D has built-in unit tests via a very simple syntax, which can appear anywhere in
|
||
a D module:
|
||
|
||
```D
|
||
unittest {
|
||
assert(myAbs(-1) == 1);
|
||
assert(myAbs(1) == 1);
|
||
}
|
||
```
|
||
|
||
#### Example
|
||
|
||
```D
|
||
import std.stdio : writeln;
|
||
|
||
struct Vector3 {
|
||
double x;
|
||
double y;
|
||
double z;
|
||
|
||
double dot(Vector3 rhs) const {
|
||
return x * rhs.x + y * rhs.y + z * rhs.z;
|
||
}
|
||
|
||
// That's okay!
|
||
unittest {
|
||
assert(Vector3(1,0,0).dot(Vector3(0,1,0)) == 0);
|
||
}
|
||
|
||
string toString() const {
|
||
import std.string : format;
|
||
return format("x:%.1f y:%.1f z:%.1f", x, y, z);
|
||
}
|
||
|
||
// … and that too!
|
||
unittest {
|
||
assert(Vector3(1,0,0).toString() == "x:1.0 y:0.0 z:0.0");
|
||
}
|
||
}
|
||
|
||
void main() {
|
||
Vector3 vec = Vector3(0,1,0);
|
||
writeln(`This vector has been tested: `, vec);
|
||
}
|
||
|
||
// Or just somewhere else
|
||
unittest {
|
||
Vector3 vec;
|
||
assert(vec.x == double.init);
|
||
}
|
||
```
|
||
|
||
#### Executing unit tests
|
||
|
||
To execute the unit tests, just compile with the `-unittest` flag. When running
|
||
the program, the unit tests get run before the main function is executed. When
|
||
using DUB, just run
|
||
|
||
```bash
|
||
dub test
|
||
```
|
||
|
||
#### Code coverage
|
||
|
||
D also supports generating coverage reports out of the box, just compile with
|
||
the `-cov` flag. When using DUB, just add the flag `--coverage`.
|
||
|
||
### String mixins
|
||
|
||
The mixin expression takes an arbitrary string and compiles it and generates instructions accordingly. This is a compile time feature, so ths string needs
|
||
to be known at compile time.
|
||
|
||
```D
|
||
mixin("int b = 5"); // compiles just fine
|
||
assert(b == 5);
|
||
|
||
static immutable compileTimeKnownString = q{int c = 5;};
|
||
mixin(compileTimeKnownString); // so does this
|
||
assert(c == 5);
|
||
|
||
immutable runTimeString = q{int c =5;};
|
||
mixin(runTimeString); // compile error, runTimeString is not known at compile time
|
||
```
|
||
|
||
### Compile Time Function Evaluation (CTFE)
|
||
|
||
In D, it is possible to execute functions at compile time. This is triggered on
|
||
the call site, i.e. whenever a function is called in a context where the result
|
||
needs to be known at compile time, the function will be evaluated at compile
|
||
time. Some of those contexts are:
|
||
* Template parameters
|
||
* Initialization of `static` variables
|
||
* Definition of `enum`s (i.e. compile time constants)
|
||
* `static assert`, `static foreach` and `static if`
|
||
* Template constraints
|
||
|
||
There are some limitations to what a function executed at compile time may do.
|
||
Essentially, the functions need to be *pure* in some sense, i.e. they may not
|
||
have side effects such as IO.
|
||
|
||
```D
|
||
import std.stdio : writeln;
|
||
|
||
auto sqrt(T)(T x) {
|
||
enum GoodEnough = 0.01;
|
||
import std.math : abs;
|
||
T z = x * x, old = 0;
|
||
int iter;
|
||
while (abs(z - old) > GoodEnough) {
|
||
old = z;
|
||
z -= ((z * z) - x) / (2 * z);
|
||
}
|
||
|
||
return z;
|
||
}
|
||
|
||
void main() {
|
||
double n = 4.0;
|
||
writeln("The sqrt of runtime 4 = ",
|
||
sqrt(n));
|
||
static cn = sqrt(4.0); // calculated at compile time
|
||
writeln("The sqrt of compile time 4 = ",
|
||
cn);
|
||
}
|
||
```
|
||
|
||
### Conditional compilation
|
||
|
||
D has several methods of conditional compilation which can be used for code
|
||
generation. D’s traits and the module `std.traits` contain many useful tools
|
||
which can be used with the following constructs.
|
||
|
||
#### `static if` & `is`
|
||
|
||
`static if` conditionally compiles a code block based on a condition that can be
|
||
evaluated at compile time:
|
||
|
||
```D
|
||
foo(int x)() { // x is a template parameter and thus known at compile time
|
||
static if(x == 0)
|
||
writeln("x is 0");
|
||
else
|
||
writeln()
|
||
}
|
||
```
|
||
|
||
The `is` expression is used to check for valid types and to compare types:
|
||
|
||
```D
|
||
foo(T)() {
|
||
static if(is(T)) {
|
||
writeln("T is a valid type");
|
||
}
|
||
static if(is(T == int)) {
|
||
writeln("T is int");
|
||
}
|
||
static if(is(T : int)) {
|
||
writeln("T implicitly converts to int");
|
||
}
|
||
}
|
||
```
|
||
|
||
#### `static foreach`
|
||
|
||
`static foreach` allows you to iterate at compile time in order to generate
|
||
code. It is best explained by an example: The code
|
||
|
||
```D
|
||
import std.stdio;
|
||
|
||
void main() {
|
||
static foreach(element; ["foo", "bar", "baz"]) {
|
||
writeln(element);
|
||
}
|
||
}
|
||
```
|
||
|
||
effectively lowers to
|
||
|
||
```D
|
||
import std.stdio;
|
||
|
||
void main() {
|
||
writeln("foo");
|
||
writeln("bar");
|
||
writeln("baz");
|
||
}
|
||
```
|
||
|
||
which means there is no loop at runtime.
|
||
|
||
#### Template constraints
|
||
|
||
A template may be defined with any number of constraints that enforce some
|
||
properties of the template parameters:
|
||
|
||
```D
|
||
void foo(T)(T value)
|
||
if (is(T : long)) { // foo!T only valid if T implicitly converts to long
|
||
}
|
||
|
||
foo(42); // compiles, 42 is of type int which implicitly converts to long
|
||
foo("someString"); // compile error, strings do not implicitly convert to long
|
||
```
|
||
|
||
```D
|
||
void bar(T)(T value)
|
||
if (__traits(compiles, {T t = T.init; t.fly();})) { // bar!T only valid if we can initialize T and T has a method called fly which takes no parameters
|
||
}
|
||
|
||
struct MyStruct {
|
||
void fly() {}
|
||
}
|
||
|
||
MyStruct myStruct;
|
||
|
||
bar(myStruct); // compiles
|
||
bar(42); // compile error
|
||
``` |