D (The Programming Language)/Printable version


D (The Programming Language)

The current, editable version of this book is available in Wikibooks, the open-content textbooks collection, at
https://en.wikibooks.org/wiki/D_(The_Programming_Language)

Permission is granted to copy, distribute, and/or modify this document under the terms of the Creative Commons Attribution-ShareAlike 3.0 License.

Contributors

Style Guides

edit

To Do List

edit

List of Contributors

edit

Add yourself to this list if you have contributed to this wikibook:

Userbox

edit

You may add this box to your user page.

 
This user contributes to the D (The Programming Language) wikibook.
{{User:Jcao219/Userboxes/DTPLContributor}}


d2/Lesson 1

Lesson 1: Hello, World!

edit

In this lesson, you will learn how to use the Phobos library to write to the console. Also, you will learn some about the structure of D programs.

Introductory Code

edit

We will start with the absolutely necessary Hello World example.

Hello World

edit
/* This program prints a
   hello world message
   to the console.  */

import std.stdio;

void main()
{
    writeln("Hello, World!");
}

Concepts

edit

In this lesson we see the import statement, the main function, the Phobos standard library in use, and also a code comment.

import std.stdio

edit

The Phobos library contains the std.stdio module which in turn contains the writeln function (along with various other functions). In order to use that function, you must first import that module. Notice that statements in D end in a semicolon.

void main()

edit

All executable D programs contain a main function. This function does not return a value, so it is declared "void" (Technically, this is an over-simplification. Return values of main will be explained in more detail later.) This function is executed when the program runs.

Function body code is enclosed in curly brackets. Although the indentation and whitespace is only for the reader, not for the compiler, make sure you indent and format your code properly and consistently; it's generally a good coding practice.

writeln and the write family

edit

writeln is for writing to the standard output (console in this case). You can supply a string such as "Hello, World!" as an argument, and that string will be printed out. There are four basic flavors of this function: With or without formatting, and with or without a trailing newline. To demonstrate how they work, all of the following are equivalent (although the first and thirds ones don't flush stdout on Windows):

// write: Plain vanilla
write("Hello, World!\n"); // The \n is a newline

write("Hello, ", "World!", "\n");

write("Hello, ");
write("World!");
write("\n");
// writeln: With automatic newline
writeln("Hello, World!");
writeln("Hello, ", "World!");
// writef: Formatted output
writef("Hello, %s!\n", "World");
// writefln: Formatted output with automatic newline
writefln("Hello, %s!", "World");
writefln("%s, %s!", "Hello", "World");
writefln("%2$s, %1$s!", "World", "Hello"); // Backwards order

/* I am a comment */

edit

The first few lines of this program are comments. They are ignored by the compiler. Block comments are enclosed in /* */. Line comments continue after // .

This is an example of a line comment:

import std.stdio; // I am a comment
void main(){} //this program does nothing

D also supports nested block comments, enclosed in /+ +/:

/+
thisIsCommentedOut();
    /+ thisIsCommentedOut(); +/
thisIsSTILLCommentedOut();
+/

This is different from ordinary block comments which behave just like in C:

/*
thisIsCommentedOut();
    /* thisIsCommentedOut(); */
thisIsNOTCommentedOut();
// The following line is a syntax error:
*/

Tips

edit
  • writeln is different from write because it adds a newline to the end.


d2/Lesson 2

Lesson 2: Types and Declarations

edit

In this lesson, you will learn how to declare and use variables.

Introductory Code

edit

The Basic Declaring of Variables

edit
import std.stdio;

int b;

void main()
{
    b = 10;
    writeln(b);  // prints 10
    int c = 11;
    // int c = 12;  Error: declaration c is already defined
    
    string a = "this is a string";
    writeln(a);  // prints this is a string
    
    // a = 1;  Error, you can't assign an int to a string variable

    int d, e, f;  // declaring multiple variables is allowed
}

Manipulation of Variables

edit
import std.stdio;

void main()
{
    int hot_dogs = 10;
    int burgers = 20;
    
    auto total = hot_dogs + burgers;

    string text = burgers + " burgers";  // Error! Incompatible types int and string
    
    writeln(hot_dogs, " hot dogs and ", burgers, " burgers");
    writeln(total, " total items");
}

Concepts

edit

In this lesson we see the declaration and assignment of variables.

Declaration Syntax

edit

The syntax of declaring variables is
type identifier;
You can also assign a value at the same time.
type identifier = value;

Declaring Multiple Variables in the Same Line

edit

D allows you to declare multiple variables of the same type in the same line. If you write:

int i, j, k = 30;

i, j, and k are all declared as ints but only k is assigned 30. It is the same as:

int i, j;
int k = 30;

Implicit Type Inference

edit

If the declaration starts with auto, then the compiler will automatically infer the type of the declared variable. In fact, it doesn't have to be auto. Any storage class would work. Storage classes are built-in D constructs such as auto or immutable. This code declares an unchangeable variable a with a value of 3. The compiler figures out that the type is int.

immutable a = 3;

You will learn much more about storage classes later.

More about writeln

edit

writeln is a very useful function for writing to stdout. One thing is special about writeln: it can take variables of any type. It can also take an unlimited amount of arguments. For example:

writeln("I ate ", 5, " hot dogs and ", 2, " burgers.");
// prints I ate 5 hot dogs and 2 burgers.

Don't worry, there is nothing magical about writeln. All the functions in the write family are implemented (in 100% valid D code) in stdio.d, which is in the Phobos standard library.

Tips

edit
  • Syntax like this: int i, j, k = 1, 2, 3; is not allowed.
  • Syntax like this: int, string i, k; is also not allowed.
  • auto is not a type. You cannot do this: auto i; because there's no way the compiler can infer the type of i. auto is merely a storage class, which tells the compiler to infer the type from the value that you assign it to in that same line.


d2/Lesson 3

Lesson 3: Intro to Functions

edit

In this lesson, you see more of functions, which were used back in Lesson 1. If you are already familiar with another C-like language, you only need to skim this over.

Introductory Code

edit
import std.stdio;

int watch_tv(int first_watched, int then_watched, string channel = "The PHBOS Channel")
{
    writefln("Finished watching %s.", channel);
    return first_watched + then_watched;
}

int commercials(int minutes)
{
    writefln("Watching %s minutes of commercials.", minutes);
    return minutes;
}

int cartoons(int minutes)
{
    writefln("Watching %s minutes of cartoons.", minutes);
    return minutes;
}

void main()
{
    auto minutesoftv = watch_tv(commercials(10), cartoons(30));
    writeln(minutesoftv, " minutes of TV watched!");
}

/*
Output:
  Watching 10 minutes of commercials.
  Watching 30 minutes of cartoons.
  Finished watching The PHBOS Channel.
  40 minutes of TV watched!
*/

Concepts

edit

Functions

edit

Functions allow you to write more organized code. With functions, you can write some code once, and then call it whenever you need to use that code.

Function Syntax

edit

Let's take a look at the syntax:

return_type name_of_function(arg1type arg1, arg2type arg2)
{
    // function body code here
    return something;
}


d2/Lesson 4

Lesson 4: Types, Continued

edit

In this lesson, you will see all the other types. Many of these types will be familiar, but be aware that there are many differences between C and D in their built-in types.

Introductory Code

edit

All-in-One

edit
import std.stdio;

void main()
{
    bool a; // default initialized to false
    a = 1;
    a = false;

    uint aa = 53;  //unsigned int
    ulong bb = 10395632;  //unsigned long
    short cc = 100;
    ushort dd = cc;  //dd == 100
    
    byte b = 0x5E;
    writeln(b); // prints 94
    b = 110;
    ubyte c = 255; //maximum value for a ubyte
    assert(c ==  ubyte.max);
    
    short d = 50;
    long e = d;
    writeln(e); // 50
    
    float f;  // default initialized to float.nan
    float g = 3.13;
    
    double h = g * 2.5;
    
    // largest hardware implemented floating-point
    real i = 373737373737377373.0;
    writeln(i); // 3.73737e+17
    
    // these numbers are not real:
    idouble j; //default = double.nan * 1.0i
    ifloat k;
    ireal l;
    
    cfloat m = 5 + 32.325i;  // a complex number
    
    // unsigned 8-bit UTF-8
    char n;  // default initialized to 0xFF
    n = 'm';
    writeln(n);  // m
    
    // unsigned 16-bit UTF-16
    wchar o;  // default = 0xFFFF
    
    // unsigned 32-bit UTF-32
    dchar p;  // default = 0x0000FFFF
}

Concepts

edit

Assertion

edit

Using assert allows you to check if a certain expression is true or not. == means is equal to (do not confuse this with =, which is for assigning variables). The checking is done at runtime, and if whatever is inside the parentheses evaluates to false, then an Assertion Error will happen and the program will terminate.

void main()
{
    bool a = true;
    assert(a);
    
    assert(2346924);  // anything that's not 0 or false
    
    // assert(false);  Assertion failure
    
    int i = 4628;
    assert(i == 4628);  // (i == 4628) evaluates to true
    assert(i != 4528);  // (i != 4528) evaluates to true
    assert(i >= 0); // yes, i is greater or equal to 0
}

Properties and Default Initializers

edit

Types have default initializers. That means, if they are declared but not assigned any value, they will be equal to something by default.

int i;  //i = int.init
assert(int.init == 0);  // i is definitely 0
float b = float.init;  //b = float.nan

Properties can be queried from any type or object. When a property of a type or object is queried, a dot goes between the property name and the identifier.

type.property

The init property of any type contains the value that objects of such a type are initialized to by default. For numeral types, you can find the min and max values with their respective properties: type.min and type.max. You can also find the size of a type in memory: type.sizeof. If you want to declare but don't want to initialize something, you can do this:

int i = void;

Then, i is uninitialized; its value is undefined garbage.

A Bit of D's Lexical Grammar

edit

D allows you to write numbers in a few useful bases and forms. So, if you wanted to add the hexadecimal number of A4 to one million three hundred thirteen thousand six hundred thirty-seven and the binary number of 1011010101, you can write this:

import std.stdio;

void main()
{
    writeln(0xA4 + 1_113_637 + 0b10_11_010101);
    // hex numbers are prefixed by 0x    
    // binary numbers by 0b
    // the answer is 1114526
}

Note that underscores in the middle of numbers are completely ignored. They are 100% optional and are useful for formatting long numbers.

Tips

edit
  • You may look through this reference for more information about D's lexical grammar. Soon a lesson will be written that contains up-to-date information on the subject.


d2/Lesson 5

Lesson 5: Type Conversion

edit

In this lesson, you will learn how the types of variables can be implicitly and explicitly converted.

Introductory Code

edit
import std.stdio;
 
void main()
{
    short a = 10;
    int b = a;
 
    // short c = b;
    // Error:  cannot implicitly convert 
    // expression b of type int to short
 
    short c = cast(short)b;
 
    char d = 'd';
    byte e = 100;
    wchar dw = 'd';
 
    int f = d + e + dw;
    writeln(f); //300
 
    float g1 = 3.3;
    float g2 = 3.3;
    float g3 = 3.4;
    int h = cast(int)(g1 + g2 + g3);
    writeln(h); //10
    int i = cast(int)g1 + cast(int)g2 + cast(int)g3;
    writeln(i); //9
}

Concepts

edit

Implicit Integral Conversions

edit

An object of an integral type can be converted to another object of an integral type as long as the destination type is wider than the original type. These conversions are implicit:

boolint
byteint
ubyteint
shortint
ushortint
charint
wcharint
dcharuint

Explicit Conversions

edit

Casting is a way to tell the compiler to try to force an object to change type. In D, you do this by writing cast(type).

Tips

edit
  • You cannot convert an integral value to a string (or the other way around) with a cast. There is a library function which you will learn later for that.


d2/Lesson 6

Lesson 6: Modules

edit

In this lesson, you will gain an understanding of D's module system. A module is a unit of code that contains definitions for variables, functions, and other D constructs that belong together. This is useful for creating reusable code. You've seen modules in every single lesson. In fact, all D source files are modules. The stdio module contains functions like writeln. The module stdio is written in a file called stdio.d at dmd/src/phobos/std/stdio.d. It contains code that is useful for console input and output and file input and output. It is within a package called std, which also contains modules such as std.algorithm or std.string, which you will learn about later. The Phobos library is just a big collection of modules that comes with a D compiler.

Introductory Code

edit
// main.d
module main;

import std.stdio;
import module1;
import byebye : sayBye;
 
void main()
{
    happy();
    writeln("Mothers Day!");
    sayBye();
    // sayByeInItalian(); undefined identifier 'sayByeInItalian'
}
// happy.d
module module1;

import std.stdio;

void happy()
{
    write("Happy ");
}

private void sad()
{
    write("Who gives a darn about ");
}

private:
void mad() { }
void bad() { }
//bye.d
module byebye;
import std.stdio;

void sayBye()
{
    writeln("Goodbye!");
}

void sayByeInItalian()
{
   writeln("Arrivederci");
}

Compile like this: dmd main.d happy.d bye.d

Concepts

edit

A Deeper Look at Imports

edit

All D code is inside modules. In D programs, there has to be one module with a main function. If one module imports another module, the imported module's definitions becomes available to the first module. In the introductory code, the main module imported two other modules: module1 and byebye. However, when byebye was imported, only one function was imported: the sayBye function. The syntax for that is import modulename : identifier1, identifier2, identifier3;.

Module Declarations

edit

D source files can specify the name of the module and what package it belongs to with a module declaration, which goes at the beginning. For example, in the stdio module, there is this line:

module std.stdio;

That line specifies the name of the module (stdio) and the package it is located in (std). Each source file can only have a maximum of one module declaration. If there is none, the source file name (without the extension) becomes the module name.

Visibility

edit

Before the void in the definition of sad in module1, you see the modifier private. That means that the sad function cannot be imported. You wouldn't be able to do import module1 : sad, either. If you see a line that says private:, everything below that line is private. Likewise, if you see private { } , everything inside the curly brackets is private.

Tips

edit
  • The name of the module does not have to relate to the names of the definitions within that module.
  • The name of the module does not have to relate to the file name.
  • There is also a public modifier, which is the opposite of the private modifier. Declarations marked by neither public nor private are public by default.
  • All D source files are modules, even if there is no module declaration in the file.


d2/Lesson 7

Lesson 7: Pointers, Pass-By-Reference and Static Arrays

edit

In this chapter, you will learn about how to use pointers and static arrays in D. You will also learn how to pass by reference without using pointers. The lesson text will not explain the concepts behind pointers and arrays, and it will assume that you have that knowledge from C or C++. D also has dynamic arrays and array slices, both of which will be covered in a later lesson.

Introductory Code

edit

Using Pointers

edit
import std.stdio;

void add_numbers(int* a, int* b)
{
    *a = *a + *b;
}

void main()
{
    int a = 50;
    int* b = &a;
    *b = 30;
    writeln(a);  // 30
    
    // int* error = &50; Error: constant 50 is not an lvalue
    
    int c = 2;
    int d = 2;
    add_numbers(&c, &d);
    writeln(c);  // 4
}

Pass By Reference

edit

If you're a C++ programmer and you read the above thinking it's a step back from C++ references, then rejoice: while D supports pointers, it also supports better solutions for passing variables by reference to functions:

import std.stdio;

void add_numbers(ref int a, int b)
{
    a += b;
}

void main()
{
    int c = 2;
    int d = 2;
    add_numbers(c, d);
    writeln(c); // 4

    // add_numbers(4, 2); Error: integer literals are not lvalues and cannot be passed by reference
}

There is also special support for out parameters:

import std.stdio;

void initializeNumber(out int n)
{
    n = 42;
}

void main()
{
    int a;
    initializeNumber(a);
    writeln(a); // 42
}

Using out, n in initializeNumber is default initialized at the beginning of the function.

Using Static Arrays

edit
import std.stdio;

void main()
{
    int[5] a;
    a[0] = 1;
    writeln(a);  // [1, 0, 0, 0, 0]
    
    // a[10] = 3;  Error: Array index 10 is out of bounds
    
    int* ptr_a = &a[0];
    ptr_a[0] = 5;
    ptr_a[4] = 3;
    writeln(a); // [5, 0, 0, 0, 3]
    
    // ptr_a[10] = 3;  // Bad: This memory is not owned by 'a' as it is out of bounds
    // Compiler would allow it, but the result is undefined!

    int[3] b = 3;
    writeln(b);  // [3, 3, 3]
    b[] = 2;
    writeln(b);  // [2, 2, 2]
}

Concepts

edit

Reference and Dereference

edit

The syntax in D is the same as C and C++. The operator & takes the reference of an object, and * dereferences.

Pointer Types

edit

In D, a pointer to typeA is:

typeA*

and the code for declaring one is:

typeA* identifier;

.

Static Arrays

edit

Static arrays have fixed lengths. You declare them like this:

byte[10] array_of_ten_bytes;

You can also do this:

byte[2] array_of_two_bytes = [39, 0x3A];

Arrays are indexed starting with the number zero for the first element. The compiler will catch you if you attempt to access an element with an index greater than or equal to that array's length. You access elements of an array like this:

my_array[index]

You can also fill an array with a single value, so that all of that array's elements equal that value:

int[7] a = 2;
writeln(a);  // [2, 2, 2, 2, 2, 2, 2]
a[] = 7;
writeln(a);  // [7, 7, 7, 7, 7, 7, 7]

As Function Arguments

edit

Look at this code:

import std.stdio;

void addOne(int[3] arr)
{
    arr[0] += 1;
    // this means arr[0] = arr[0] + 1
}

void main()
{
    int[3] array1 = [1, 2, 3];
    addOne(array1);
    writeln(array1); // [1, 2, 3]
}

The output is [1,2,3], not [2,2,3]. Why? It's because static arrays are copied whenever they are passed as arguments to a function. The arr in the addOne function is only a copy of array1. Rewrite it like this:

import std.stdio;

void addOne(ref int[3] arr)
{
    arr[0] += 1;
}

void main()
{
    int[3] array1 = [1, 2, 3];
    addOne(array1);
    writeln(array1); // [2, 2, 3]
}


d2/Lesson 8

Lesson 8: Strings and Dynamic Arrays

edit

In this chapter, you will learn about strings in depth. In the meantime, you will also learn about another type: the dynamic array.

Introductory Code

edit

Dynamic Arrays

edit
import std.stdio;

void main()
{
    int[] a = [1,2,3,4];
    int[] b = [5,6];
    auto c = a ~ b;
    writeln(c); // [1,2,3,4,5,6]
    
    writeln(c.length);  // 6
    
    int* ptr_c = c.ptr;
    ptr_c[0] = 3;
    writeln(c); // [3,2,3,4,5,6]
}

Immutable and Strings

edit
import std.stdio;

void main()
{
    // Concept:  Immutable
    immutable(int) a = 10;
    // a = 11;  Error:  cannot modify immutable
    immutable a = 10;
    
    // Concept:  Strings as Arrays
    immutable(char)[] str = "Hello";
    auto str1 = str ~ "1";
    writeln(str1); // Hello1
    writeln(str1.length);  // 6  
    // str1[] = 'z'; error! str1's elements are not mutable
    char[] mutablestr1 = str1.dup;
    
    // Concept: Char Literals
    mutablestr1[] = 'z';  // 'z' is not a string, but a char
    
    str1 = mutablestr1.idup;  // The str1 itself is mutable
    // only its elements are not mutable
    writeln(str1);  // zzzzzz
}

Concepts

edit

Dynamic Arrays

edit

Dynamic Arrays versus Static Arrays

edit

In the previous lesson, you learned about static arrays. Dynamic arrays are different from those in that they do not have a fixed length. Essentially, a dynamic array is a structure with this info:

  1. A pointer to the first element
  2. The length of the entire array

You create a dynamic array like this:

int[] a;

You can create a dynamic array initiated with a specific length:

int[] a = new int[](5);

You will learn more about the new keyword later.
The syntax for filling an array with a single value is the same for both static and dynamic arrays:

int[] a = [1,2,3];
a[] = 3;
writeln(a); // [3, 3, 3]

Dynamic arrays are indexed in the same fashion as static arrays. The syntax for accessing an element at a certain index is the same:

a[2];

However, since dynamic arrays don't have a length known at compile-time, the compiler can't check if the index that you are accessing is really within that length. Code like this would compile but would also cause a runtime Range Violation error:

int[] a = [1,2,3];
writeln(a[100]);  //Runtime error, Range violation

Manipulation of Dynamic Arrays

edit

Dynamic arrays can be combined with other dynamic arrays or even with static arrays using the ~ operator. Appending a new element to the array uses the same operator.

int[] a = [1,2];
auto b = a ~ [3,4];
writeln(b);  //[1, 2, 3, 4]
b ~= 5;  // same as b = b ~ 5;
writeln(b);  //[1, 2, 3, 4, 5]

You shouldn't assign a dynamic array to a static array (my_static_arr = my_dynamic_arr;) unless if you are certain that my_dynamic_arr.length is the equal to my_static_arr.length. Otherwise, a runtime error will occur. You can always assign a static array to a dynamic array variable, though, since the dynamic array will automatically resize if the static array's length is too big.

int[] a = [9,9,9,9,9,9];
int[3] b = [1,2,3];
int[200] c;
a = b;
writeln(a);  // [1, 2, 3]
a = c;
writeln(a.length); // 200

int[] d = [5,4,3];
b = d;  // OK: lengths are both 3
// c = d;  runtime error! lengths don't match.

Passing Dynamic Arrays to Functions

edit

Dynamic Arrays are passed by value to functions. That means, when you pass a dynamic array to a function, the structure that contains the pointer to the first element and the length is copied and passed.

void tryToChangeLength(int[] arr)
{
    arr.length = 100;
}
void main()
{
    int[] a = [1,2];
    tryToChangeLength(a);
    writeln(a.length);  // still 2
}

You learned in the last chapter that you can cause things to be passed by reference instead of by value by adding the ref modifier.

Array and Other Properties

edit

These properties apply to both static and dynamic arrays:

Property Description
.init For static arrays, it returns an array with each element initialized to its default value.
.sizeof Returns the size of the array in memory.
.length Returns the number of elements in the array. This is fixed in static arrays.
.ptr Returns a pointer to the first element of the array.
.dup Creates a dynamic array that is a copy of the array and returns it.
.idup Similar to .dup but the copy's elements are immutable.
.reverse Returns the array with its elements in reverse order.
.sort Sorts in place the elements in the array and returns the result.

There are also other properties which are common to every object or expression. Two of such properties are the .init and .sizeof properties.

Immutable

edit

In D, immutable is a storage-class just like auto. It converts a type to a non-modifiable type.

immutable(int) fixed_value = 37;
immutable int another_value = 46;

Note that immutable(type) means the same as immutable type to the compiler.

Storage-Classes and auto

edit

When you have a storage class like immutable, you can omit auto for type inference.

immutable fixed_value = 55;

The type of whatever is immutable is inferred from the compiler. This next code example is not valid because there is no way the compiler could infer the type:

immutable fixed_value;  //Error!

Using immutable Variables

edit

This is allowed and perfectly fine code:

immutable(int) a = 300;
int b = a;

It only sets b equal to the value of a. b does not have to be immutable. That changes if you are taking a reference:

immutable(int) a = 13;
immutable(int)* b = &a;
// int* c = &a;  Error.

You are allowed to cast the immutable away, but if you were to modify an immutable value using that hack, the result is undefined.

immutable(int) a = 7;
int* b = cast(int*)&a;
// Just make sure you do not modify a
// through b, or else!

Strings as Arrays

edit

You've seen strings since Lesson 1. A string is the exact same thing as immutable(char)[], a dynamic array of immutable char elements. Likewise, a wstring is the same as immutable(wchar)[], and a dstring is the same as immutable(dchar)[].

String Properties

edit

Strings have the same built-in properties as dynamic arrays. One useful property is the .dup property, for creating a mutable char[] copy of a string if you want to modify the individual characters of the string.

string a = "phobos";
char[] b = a.dup;
b[1] = 'r';
b[4] = 'e';
writeln(b);  // probes

The .idup property is useful for creating a copy of an existing string, or for creating a string copy of a char[].

string a = "phobos";
string copy_a = a.idup;
char[] mutable_a = copy_a.dup;
mutable_a[3] = 't';
copy_a = mutable_a.idup;
writeln(mutable_a); // photos
writeln(a); // phobos

Char Literals

edit

A char literal is enclosed by single-quotes. There are also wchar and dchar literals.

auto a = "a"; // string
auto b = 'b'; // char
auto c = 'c'c; // char
auto d = 'd'w; // wchar
auto e = 'e'd; // dchar


d2/Lesson 9

Lesson 9: Slicing and a Deeper Look into Dynamic Arrays

edit

Slicing in D is one of the language's most powerful and useful aspects. This lesson is really more of a continuation of the last lesson - you will also get a deeper look into how D's arrays work.

Introductory Code

edit
import std.stdio;

void writeln_middle(string msg)
{
    writeln(msg[1 .. $ - 1]);
}

void main()
{
    int[] a = [1,3,5,6];
    a[0..2] = [6,5];
    writeln(a); // [6, 5, 5, 6]
    a[0..$] = [0,0,0,0];
    writeln(a); // [0, 0, 0, 0]
    
    a = a[0 .. 3];
    writeln(a); // [0, 0, 0]
    a ~= [3,5];
    writeln(a); // [0, 0, 0, 3, 5]
    
    int[] b;
    b.length = 2;
    b = a[0 .. $];
    writeln(b.length); // 5
    b[0] = 10;
    
    writeln(b); // [10, 0, 0, 3, 5]
    writeln(a); // [10, 0, 0, 3, 5]
    
    writeln_middle("Phobos");  // hobo
    writeln_middle("Phobos rocks");
}

Concepts

edit

What are Slices?

edit

You can take slices of arrays in D with this syntax:

arr[start_index .. end_index]

The element at end_index is not included in the slice. Remember that dynamic arrays are just structures with a pointer to the first element and a length value. Taking a slice of a dynamic array simply creates a new one of those pointer structures that points to the elements of that same array.

string a = "the entire part of the array";
string b = a[11 .. $]; // b = "part of the array"
// b points to the last 17 elements of a
// If you modify individual elements of b, a will also
// change since they point to the same underlying array!

Three Ways to Do the Same Thing

edit

Notice that the $ is automatically replaced with the length of the array being sliced from. The following three lines are equivalent and both create slices of the entirety of an array arr.

char[] a = arr[0 .. $];
char[] a = arr[0 .. arr.length];
char[] a = arr[]; // shorthand for the above

A Visual Representation

edit

 

The .capacity Property

edit

All dynamic arrays in D have a .capacity property. It is the maximum number of elements that can be appended to that array without having to move the array somewhere else (reallocation).

int[] a = [1,2,3,45];
writeln("Ptr: ", a.ptr);
writeln("Capacity: ", a.capacity);
a.length = a.capacity; // the array reaches maximum length
writeln("Ptr: ", a.ptr, "\nCapacity: ", a.capacity);  // Still the same
a ~= 1;  // array has exceeded its capacity
// it has either been moved to a spot in memory with more space
// or the memory space has been extended
// if the former is true, then a.ptr is changed.

writeln("Capacity: ", a.capacity);  // Increased

Maximizing Efficiency by Only Reallocating When Necessary

edit

It is good for efficiency to make sure that appending and concatenation do not cause too many reallocations because it is an expensive procedure to reallocate a dynamic array. The following code may reallocate up to 5 times:

int[] a = [];
a ~= new int[10];
a ~= [1,2,3,4,5,6,7,8,9];
a ~= a;
a ~= new int[20];
a ~= new int[30];

Make sure that the array capacity is big enough at the beginning to allow for efficient non-reallocating array appends and concatenations later if performance is a concern. You can't modify the .capacity property. You are only allowed to either modify the length, or use the reserve function.

int[] a = [1,2,3,45];
a.reserve(10);  // if a's capacity is more than 10, nothing is done
// else a is reallocated so that it has a capacity of at least 10

When Passed to Functions

edit

Remember that D's arrays are passed to functions by value. When static arrays are passed, the entire array is copied. When a dynamic array is passed, only that structure with the pointer to the underlying array and the length is copied - the underlying array is not copied.

import std.stdio;

int[] a = [1,2,3];
void function1(int[] arr)
{
    assert(arr.ptr == a.ptr);  // They are the same
    
    // But the arr is not the same as a
    // If arr's .length is modified, a is unchanged.
    
    // both arr and a's .ptr refer to the same underlying array
    // so if you wrote: arr[0] = 0;
    // both arr and a would show the change, because they are both
    // references to the same array.
    
    // what if you forced arr to reallocate?
    arr.length = 200;  // most likely will reallocate
    
    // now arr and a refer to different arrays
    // a refers to the original one, but
    // arr refers to the array that's reallocated to a new spot
    arr[0] = 0;
    writeln(arr[0]);  // 0
    writeln(a[0]);  // 1
}

void main()
{
    function1(a);
}

As you can see, there are a few possibilities if you pass a dynamic array to a function that looks like this:

void f(int[] arr)
{
    arr.length = arr.length + 10;
    arr[0] += 10;
}
  • First possibility: the array's capacity was large enough to accommodate the resizing, so no reallocation occurred. The original underlying array's first element was modified.
  • Second possibility: the array's capacity was not large enough to accommodate the resizing, but D's memory management was able to extend the memory space without copying the entire array. The original underlying array's first element was modified.
  • Third possibility: the array's capacity was not large enough to accommodate the resizing. D's memory management had to reallocate the underlying array into a completely new space in memory. The original underlying array's first element was not modified.

What if you want to make sure that the following works?

int[] a = [0,0,0];
f(a);
assert(a[0] == 10);

Simply change the function f so that dynamic arrays are passed by reference:

void f(ref int[] arr)
{
    arr.length = arr.length + 10;
    arr[0] += 10;
}

Appending to Slices

edit

When you take a slice of a dynamic array, and then append to that slice, whether the slice is reallocated depends on where the slice ends. If the slice ends in the middle of the data from the original array, then appending to that slice would cause a reallocation.

int[] a = [1,2,3,4];
auto b = a[1 .. 3];
writeln(b.capacity);  // 0
// b cannot possibly be appended
// without overwriting elements of a
// therefore, its capacity is 0
// any append would cause reallocation

Lets say you took a slice of a dynamic array, and that slice ends at where the dynamic array ends. What if you appended to the dynamic array so that the slice no longer ends at where the dynamic array's data ends?

int[] a = [1,2,3,4];
writeln(a.capacity);  // 7
auto b = a[1 .. 4];
writeln(b.capacity);  // 6
a ~= 5;  // whoops!
// now the slice b does *not* end at the end of a
writeln(a.capacity);  // 7
writeln(b.capacity);  // 0

The .capacity property of a slice does indeed depend on other references to the same data.

Assignment-To-Slices

edit

An Assignment-To-Slice looks like this:

a[0 .. 10] = b

You are assigning b to a slice of a You have actually seen Assignment-to-Slices in the last two lessons, even before you learned about slices. Remember this?

int[] a = [1,2,3];
a[] = 3;

Remember that a[] is shorthand for a[0 .. $] When you assign a int[] slice to a single int value, that int value is assigned to all the elements within that slice. An Assignment-To-Slice always causes data to be copied.

int[4] a = [0,0,0,0];
int[] b = new int[4];
b[] = a;  // Assigning an array to a slice
// this guarantees array-copying
a[0] = 10000;
writeln(b[0]); // still 0

Beware! Whenever you use Assignment-To-Slice, the left and right sides' .length values must match! If not, there will be a runtime error!

int[] a = new int[1];
a[] = [4,4,4,4];  // Runtime error!

You also must make sure that the left and right slices do not overlap.

int[] s = [1,2,3,4,5];
s[0 .. 3] = s[1 .. 4];  // Runtime error! Overlapping Array Copy

Vector Operations

edit

Let's say you wanted to double each and every integer element of an array. Using D's vector operation syntax, you can write any of these:

int[] a = [1,2,3,4];
a[] = a[] * 2;  // each element in the slice is multiplied by 2
a[0 .. $] = a[0 .. $] * 2;  // more explicit
a[] *= 2 // same thing

Likewise, if you wanted to perform this operation: [1, 2, 3, 4] (int[] a) + [3, 1, 3, 1] (int[] b) = [4, 3, 6, 5] You would write this:

int[] a = [1, 2, 3, 4];
int[] b = [3, 1, 3, 1];
a[] += b[];  // same as a[] = a[] + b[];

Just like for Assignment-To-Slice, you have to make sure the left and right sides of vector operations have matching lengths, and that the slices do not overlap. If you fail to follow that rule, the result is undefined (There would be neither a runtime error nor a compile-time error).

Defining Array Properties

edit

You can define your own array properties by writing functions in which the first argument is an array.

void foo(int[] a, int b)
{
    // do stuff
}
void eggs(int[] a)
{
    // do stuff
}
void main()
{
    int[] a;

    foo(a, 1);
    a.foo(1);	// means the same thing

    eggs(a);
    a.eggs;  // you can omit the parentheses
    // (only when there are no arguments)
}

Tips

edit
  • Steven Schveighoffer's article "D Slices" is an excellent resource if you want to learn more.


d2/Lesson 10

Lesson 10: Conditionals and Loops

edit

Conditionals and loops are essential for writing D programs.

Introductory Code

edit

Palindrome Checker

edit
module palindromes;

import std.stdio;

bool isPalindrome(string s)
{
    int length = s.length;
    int limit = length / 2;
    for (int i = 0; i < limit; ++i)
    {
        if (s[i] != s[$ - 1 - i])
        {
            return false;
        }
    }
    return true;
}

void main()
{
    string[] examples = ["", "hannah", "socks", "2002", ">><<>>", "lobster"];
    foreach(e; examples)
    {
        if(!e.length) continue;  // skip that empty string

        if(isPalindrome(e))
            writeln(e, " is an example of a palindrome.");
        else
            writeln(e, " is an example of what's not a palindrome.");
    }
    while(true)
    {
        write("Type any word: ");
        string input = readln();
        if(input.length <= 1)  // length == 1 means input == "\n"
            break;  // nothing was typed
        input = input[0 .. $-1];  // strip the newline
        if(isPalindrome(input))
            writeln(input, " is a palindrome.");
        else
            writeln(input, " is not a palindrome.");
    }
}

More Conditionals and Branching

edit
import std.stdio;

string analyzeHoursOfSleep(int hours)
{
    if(!hours) return "You didn't sleep at all.";
    
    string msg = "";
    switch(hours)
    {
        case 1,2,3:
            msg ~= "You slept way too little!  ";
            goto case 7;
        case 4: .. case 6:
            msg ~= "Take a nap later to increase alertness.  ";
        case 7:
            msg ~= "Try to go back to sleep for a bit more.  ";
            break;
        default:
            msg ~= "Good morning.  Grab a cup of coffee.  ";
    }
    return msg ~ '\n';
}

void main()
{
    writeln(analyzeHoursOfSleep(3));
    writeln(analyzeHoursOfSleep(6));
    writeln(analyzeHoursOfSleep(7));
    writeln(analyzeHoursOfSleep(13));
    
    int i = 0;
L1: while(true)
    {
        while(true)
        {
            if(i == 3)
                break L1;
            i++;
            break;
        }
        writeln("Still not out of the loop!");
    }
}

/*
Output:
You slept way too little!  Try to go back to sleep for a bit more.

Take a nap later to increase alertness.  Try to go back to sleep for a bit more.

Try to go back to sleep for a bit more.

Good morning.  Grab a cup of coffee.

Still not out of the loop!
Still not out of the loop!
Still not out of the loop!
*/

Concepts

edit

The if and else Statements

edit

Using if allows you to make part of your code only execute if a certain condition is met.

    if(condition that evaluates to true or false)
    {
        // code that is executed if condition is true
    } else {
        // code that is executed if condition is false
    }

In fact, if the section of code that's inside the if or else is only one line long, you can omit the curly brackets.

    if(condition1) do_this();
    else if(condition2) do_that();  // only executed if condition1 is false, but
    // condition2 is true
    else do_the_other_thing();  // only executed if both condition1 and condition2 are false

As a result, this is often seen:

   if (condition1) {
       do_something1();
       something_more1();
   } else if(condition2) {
       do_something2();
       something_more2();
   } else if(condition3) {
       do_something3();
       something_more3();
   } else if(condition4) {
       do_something4();
       something_more4();
   } else {
       do_something_else();
   }

The Condition

edit

The condition that goes inside of the parentheses in conditional statements such as if can be anything convertible to bool. That includes integral and floating-point types (true if nonzero, false if otherwise) and pointers (null is false, and dynamic arrays (always true).

The while Loop

edit

A while loop will allow you to repeat a block of code as long as a certain condition is met. There are two forms of the while loop:

   while(condition1) {
       do_this();
   }

and

   do {
       do_this();
   } while(condition1)

The difference is, in the first example, if condition1 is false, do_this is never called, while in the second example it would be called once (the conditional check happens after the code is executed once).

The foreach Loop

edit

This loop is for iteration. Take a look at these two ways to use foreach:

foreach(i; [1,2,3,4]) {
    writeln(i);
}
foreach(i; 1 .. 5) { writeln(i); }  // equivalent to above

The for Loop

edit

This type of looping is the most complex, but it is also the one that gives a lot of control. It is defined in the same way as other C-like languages:

   for(initialization; condition; counting expression) { ... }

The initialization expression is executed only once during the beginning. Then condition is checked to be true or false. If it is true, the code inside of the conditional block (inside of the brackets) is executed. After that execution, the counting expression is executed. Then, the condition is checked, and if it is true, the loop continues. For example:

for(int i=0; i <= 5; i++)
{
    write(i);
}
// output: 012345

You can even omit parts of what goes inside the parentheses of the for. These two are equivalent:

for(int i=0; i==0; ) {
    i = do_something();
}
int i = 0;
while(i == 0) {
    i = do_something();
}

break and continue

edit

These are two statements that are used inside of loops.

The break statement breaks out of the loop. Whenever the break statement is encountered, the loop is immediately exited. This statement can go inside of while, for, foreach, and switch blocks (you will learn about those later).

The continue statement causes a loop to restart at the beginning. Let's see, through code, exactly how this works. This code example counts to 7 but skips 5.

for(int i = 0; i <= 7; i++)
{
    if(i == 5) continue;
    writeln(i);
}

Switches and More

edit

D allows absolute branching with labels and goto.

int i = 0;
looper:  // this is a label
write(i);
i++;
if(i < 10) goto looper;
writeln("Done!");
// 0123456789Done!

Do not use these unless if you have to. Code that uses labels can most often be written with more readable looping constructs, like for, while, and foreach.

There is something in D, C and C++ called the switch. D's switches are actually more powerful than C and C++'s switches.

switch(age)
{
   case 0,1:  // if(age == 0 || age == 1) { ... }
       writeln("Infant");
       break;
   case 2,3,4:  // else if (age == 2 || age == 3 || age == 4) { .. }
       writeln("Toddler");
       break;
   case 5: .. case 11:
       writeln("Kid");
       break;
   case 12:
       writeln("Almost teen");
       break;
   case 13: .. case 19:
       writeln("Teenager");
       break;
   default:  // else { .. }
       writeln("Adult");
}

Note that you must have a break in order to get out of the switch. Otherwise, fall-through occurs. Also, you can use goto.

int a = 3;
switch(a)
{
    case 1:
        writeln("Hello");
    case 2:
        writeln("Again");
        break;
    case 3:
        goto case 1;
    default:
        writeln("Bye");
}
/* output: Hello
           Again
*/

Strings can be used in case. This is a feature that's not in C or C++.

else

edit

(It doesn't seems to work in recent compilers).
You can use else for foreach, while, and for loops, too. If any of those loops have an else clause, then the else is only executed if the loop terminates normally (i.e. not with break).

int[] arr = [1,2,3,5,6];
foreach(item; arr)
{
    if(item == 4) break;
}
else
{
    writeln("No four found.");
}

//Output: No four found.