# 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) * [Exceptions](#exceptions) * [`nothrow`](#nothrow) * [Gems](#gems) * [Uniform function call syntax (UFCS)](#uniform-function-call-syntax-ufcs) * [Scope guards](#scope-guards) ## 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)(); ``` ### 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 */ } ```