From d15f34a5308aa273c5f7f6e5f309283dee55832e Mon Sep 17 00:00:00 2001 From: Johannes Loher Date: Fri, 13 Apr 2018 17:57:18 +0200 Subject: [PATCH] update --- hands-on_dlang.md | 452 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 426 insertions(+), 26 deletions(-) diff --git a/hands-on_dlang.md b/hands-on_dlang.md index cc6d361..7600220 100644 --- a/hands-on_dlang.md +++ b/hands-on_dlang.md @@ -24,22 +24,27 @@ echo "~/.dlang/dmd-2.079.0/activate" >> ~/.profile # Add dmd and dub to PATH on * 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. +* 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). +* 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. +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: +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 @@ -53,7 +58,8 @@ brew cask install visual-studio-code |----------------|---------| |OS X |⌘ + ⇧ + X| |Windows |⌃ + ⇧ + X| -* Install the extension “D Programming Language (code-d)” (requires that git is installed). +* Install the extension “D Programming Language (code-d)” (requires that git is + installed). * Restart Visual Studio Code. ## Basics @@ -71,9 +77,10 @@ void main() { ### 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: +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; @@ -81,7 +88,8 @@ import std.file; #### Selective imports -It is possible (and often good style) to import symbols selectively from a module: +It is possible (and often good style) to import symbols selectively from a +module: ```D import std.stdio: writeln, writefln; @@ -91,8 +99,9 @@ import std.stdio: writeln, writefln; 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: +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() @@ -114,18 +123,20 @@ D has the following basic types: | Datatypes | Size | | ------------------------------- | ------------------------------------------------------------ | -| `bool` `byte`, `ubyte`, `char` | 8-bit | +| `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. +`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`). +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: @@ -136,8 +147,9 @@ 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`. +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. @@ -162,7 +174,8 @@ And so do floating point types: #### Indexing -For indexing, usually the alias type `size_t` is used, which is large enough to represent an offset into all addressable memory. +For indexing, usually the alias type `size_t` is used, which is large enough to +represent an offset into all addressable memory. ### Variable declarations @@ -184,8 +197,8 @@ It is also possible to declare several variables at once: 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 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; @@ -197,6 +210,44 @@ Here is a combination of the above notations: 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: @@ -254,6 +305,219 @@ void fun() { 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 @@ -272,7 +536,8 @@ if (a == 5) { #### 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). +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; @@ -304,13 +569,15 @@ switch(c) { } ``` -#### Loops +#### Old fashioned loops -`while`-, `do`…`while`- and classical `for`-loops all work the same as in C++/Java etc. +`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: +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: @@ -321,3 +588,136 @@ for (int i = 0; i < 10; ++i) { } } ``` + +#### `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.