handson-dlang/hands-on_dlang.md

1064 lines
27 KiB
Markdown

# Hands-On DLang
- [Hands-On DLang](#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)
- [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](#if%E2%80%A6else)
- [switch…case](#switch%E2%80%A6case)
- [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)
## 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.079.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.079.0/dmd.2.079.0.dmg.
* Open `dmd.2.079.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.079.0/dmd-2.079.0.exe.
* Run `dmd-2.079.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.
## 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:
| Datatypes | 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 precisions |
| `.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 accross 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 implictly 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 infered.
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 `--boundcheck=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 variabe 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 atually 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 ofer 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 iterationg 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 implicitely inherits from `Object`.
```D
class Foo { } // implicitely 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. Coparing 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 instanciation:
```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 templated:
```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)();
```