# Hands-On DLang ## 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 /* … */ ```