Say hello to D
The D programming language is a multi-paradigm language that belongs to the C family. Out of the box, it supports aspects of procedural, object-oriented, generic, generative, and functional programming. That's not to say that it's an OOP language or a functional programming language, or that it can accurately be pigeonholed into any specific paradigm. The philosophy of D is to provide a range of tools that allow the programmer to have efficiency, control, and modeling power while ensuring that all of the disparate parts work smoothly together. Use object orientation where you need it, but forgo it for procedural or functional programming in other areas of your code base and be assured that it will all work together as a cohesive whole. Many D programmers will tell you that there isn't one specific feature of D that, taken in isolation, makes the language a pleasure to use. Rather, it's the sum of all the parts that keeps them writing D code.
New users coming to D from other C-family languages will find a great deal that looks familiar. That can be reassuring and makes for a good head start on learning some of D's features; caution is warranted, however. It's tempting to take that familiarity for granted and try to write D as if it were a more familiar language. For the most part, things will work as expected. D is, after all, a member of the C family. But in some cases, this is certain to lead to unexpected compiler errors, or the realization that some blocks of code aren't behaving in the manner the new D user thinks they should. In other cases, there is a more idiomatic D approach that can improve readability or maintainability, particularly when working with the standard library.
To learn a new language effectively, it's necessary to come at it with as close to a blank slate as possible. Just because features look the same on the surface as they do in another language, doesn't mean they are the same underneath. Given that premise, a consistent theme in this book is that D is not C++, Java, C#, or any language other than D. When you are writing D code, you should be thinking in D. Several of the code snippets in the book are intended not just to introduce D features, but to demonstrate explicitly how certain features differ from other languages. You are encouraged to enter such snippets into an editor yourself, especially if C++ is already in your muscle memory. Implementing the snippets as you encounter them and seeing the differences in action makes it more likely that you'll think in D instead of C++ when working on your own D projects.
Note
D started life as the Mars programming language, but Walter's friends kept calling it D. Eventually, he began to do so as well and the name stuck (see the D FAQ at http://dlang.org/faq.html#q1 for background). Neither name lends itself to productive web searches, but search engines are able to recognize the keyword dlang. Use that in your list of search terms rather than simply using D and you should be rewarded with a number of relevant hits.
An introductory program
This section presents a simple D program demonstrating a handful of language and library features. A brief description of each feature is then given, along with a reference to the chapter in which it is explained in more detail. Anyone familiar with C should be able to follow the code rather easily, though there might be a couple of features that seem unusual. If you happen to find any of it confusing, don't worry about it for now. Each feature will be explained later in the book.
In preparation for implementing all of the code snippets and examples in this book, it's a good idea to create a directory somewhere that's easy to navigate to from the command line—for example C:\LearningD
or ~/learningd
. However you choose to name this directory, it will be referred to as $LEARNINGD
throughout the book. Each chapter should have its own subdirectory. The example in this section should be saved as $LEARNINGD/Chapter01/hello.d
.
Note
Note that I prefer to use forward slashes when I type source paths, unless I'm talking specifically about Windows, which means Windows users should convert them to backslashes as needed.
Let's look at the example now:
import core.thread; import std.stdio; void main() { import std.range : iota, retro; write("Greeting in "); foreach(num; iota(1, 4).retro) { writef("%s...", num); stdout.flush(); Thread.sleep(1.seconds); } writeln(); writeln("Hello world!"); }
The first thing to understand is that all D source files serve a purpose beyond just being text files on your computer. A D source file is also a D module. Most D users use the terms
source file and module interchangeably. A
module is one of several levels of encapsulation in a D program (we'll talk more about encapsulation in Chapter 3, Programming Objects the D Way). If you compile a D source file named hello.d
, then the compiler will read it into memory and your program will contain a single module named hello
. This is the default behavior. Since we are implementing a simple one-module program, we'll just accept the default and put off learning how to override it until Chapter 2, Building a Foundation with D Fundamentals.
The first two lines of the example look like this:
import core.thread; import std.stdio;
An import declaration tells the compiler to look up a given module, specified by the name immediately following the import
keyword, and then make some or all of its symbols available within a specific section of the current module. In the format we use here, in which no symbols are specified, all publicly visible symbols in the imported module are made available. The location of the declaration is what determines the section, or scope, in which the symbols will be available. In this case, since the declarations are in module scope and no specific symbols are listed, the net effect is that every publicly visible symbol in the core.thread
and std.stdio
modules is available for use throughout the entirety of our hello
module.
Consider the module name std.stdio
. Each part of the name has a defined meaning. Although the term
module name is used to refer to the full name, the part to the right of the dot, stdio
, is the actual module name. The part to the left of the dot, std
, is the name of a package. Packages are used to group modules into hierarchies. A module can belong to only one package, but that package can be a subpackage of another package, which can be a subpackage of another package, and so on. This means you can have multiple package names on the left side of the dot, such as mylib.data.formats.json
. Here, we have three packages and are referring to a module called json
. The package named formats
is a subpackage of data
, which is a subpackage of the top-level, or root, package called mylib
. There's more to say about modules and import declarations in Chapter 2, Building a Foundation with D Fundamentals.
The std
and core
packages are available with any D compiler; the former is part of Phobos, the D standard library, and the latter is part of the runtime library, DRuntime. std.stdio
makes available everything needed for basic file I/O, including reading from standard input (stdin
) and writing to standard output (stdout
). The module core.thread
provides facilities for creating new threads and affecting the execution of the current thread.
Now take a look at the next line:
void main() {
Every D program requires a function named main
. When a program is first launched, control passes from the operating system to the C runtime and from there to the D runtime. Finally, main
is called and the program takes control. We'll look at this in a little more detail in Chapter 3, Programming Objects the D Way, when we'll also see that it's possible to execute code before main
is called.
There are four fundamental alternatives for declaring a main
function. Which one to choose is entirely dependent on the requirements of the program:
void main() {} void main(string[] args) {} int main() { return 0; } int main(string[] args) { return 0; }
The first two versions are ultimately equivalent to the latter two; the compiler will ensure that they actually return 0
upon successful execution. Execution is considered to fail when an exception is thrown (exceptions are introduced in Chapter 3, Programming Objects the D Way). For most of the examples in this book, the first signature is all we need. Except for a couple of snippets later on, we aren't going to parse any command line arguments, so we can dispense with forms that accept an array of strings
. We also aren't writing any programs that need to pass a return value to the OS on exit, so we have no need of the versions with the int
return.
Note
Windows programmers might be wondering how D handles WinMain
. DRuntime knows only of main
, so if WinMain
is used as the program entry point, then all of the initialization normally carried out by DRuntime must be handled manually. We'll learn more about DRuntime later.
Let's get back to the code. The next line is another import declaration, one which differs from the two declarations at the top of the module:
import std.range : iota, retro;
Because this declaration is inside a function, it is called a
scoped import. Symbols made visible by scoped imports are only visible inside the scope in which the declaration is made. In this case, the symbols are visible only inside main
. There's more to this declaration, though. Notice the colon, followed by iota
and retro
. In an import declaration, a colon followed by a comma-separated list of symbols means that only the listed symbols will be visible. In this case, no symbols from std.range
are visible in main
other than iota
and retro
. We'll see what they do shortly.
It's time for a line that actually puts something on the screen. For that, we're going to invoke a handy and very flexible function from the std.stdio
module:
write("Greeting in ");
The write
function is one of a handful of functions that print text strings to standard output. It's analogous to the C standard library function puts
, but it differs in that it can take any number of arguments of any type. Each argument will be printed in the order they are given to the function, with no spaces added between them. For example:
write("Happy ", 100, "th birthday to", "you!")
This prints the text Happy 100th birthday to you!
.
The next line introduces three items:
foreach(num; iota(1, 4).retro) {
The foreach
loop is a loop construct that can be used to iterate over a range. iota
is a function that returns a range of numbers, in this case from 1 to 3. retro
is a function that takes a range as input and returns a new one containing the elements of the original range in reverse order. The ultimate result of this line is a loop iterating over the numbers 3
, 2
, 1
. The foreach
loop is described in Chapter 2, Building a Foundation with D Fundamentals. The entirety of Chapter 6, Understanding Ranges, is devoted to explaining ranges, an integral part of D. Both the iota
and retro
functions are described in Chapter 7, Composing Functional Pipelines with Algorithms and Ranges.
It's worth noting here that iota(1, 4).retro
is the same as retro(iota(1, 4
)). The former syntax is possible because of a feature called Uniform Function Call Syntax (UFCS). Given a function func
and a function argument arg
, func(arg)
can be written as arg.func()
. You'll learn more about UFCS in Chapter 2, Building a Foundation with D Fundamentals.
Next up are the three lines of the foreach
loop:
writef("%s...", num); stdout.flush(); Thread.sleep(1.seconds);
The writef
function is a variation of write
that prints a formatted text string to standard output. It's analogous to the C standard library function printf
; with .stdout
is a global instance of a type called File
, both of which are declared in std.stdio
.
When writing to a file handle, the operating system buffers text internally for efficiency. Normally, when the handle belongs to a console or terminal, line buffering is enabled. This means that the buffer is flushed when a newline character is printed to the output stream. In this example, calling flush
manually flushes the buffer in order to achieve the effect of having one number printed per second; otherwise, it would all be printed at once after the loop exits and the first call to writeln
executes. This effect is regulated by the call to Thread.sleep
, which causes execution of the process to pause for one second.
Note that the call to Thread.sleep
is not using UFCS. Thread
is a class, and sleep
is a static member function. 1.seconds
, however, does use UFCS. The function seconds
is declared in a runtime module named core.time
. This module is imported indirectly by core.thread
such that all of its symbols are visible. 1.seconds
is the same as seconds(1)
(parentheses on function calls are sometimes optional). This function returns an instance of the Duration
type, which sleep
uses to determine how long to pause the current thread. Public imports and function call syntax are discussed in Chapter 2, Building a Foundation with D Fundamentals. Classes and member functions are introduced in Chapter 3, Programming Objects the D Way.
Finally, the last two lines of the example:
writeln(); writeln("Hello world!");
The writeln
function is identical to write
, but has one additional feature: it appends a newline character to the output. Here, we call it twice. The first call appends a newline to the text that was written in the loop, while the second prints the greeting. This could be condensed to one line as writeln("\nHello world!")
. Note that there is also a formatting version of this function called writefln
.
In order to verify that this program works as expected, it will need to be compiled and executed. Instructions on how to do so will be discussed later in the chapter.
Getting help
In your journey with D, you're inevitably going to need assistance. There are a couple of primary online locales where experienced D users can be found answering questions from not-so-experienced D users and having fierce debates about language features, as passionate programmers are known to do.
The first place any new D user should look is http://forum.dlang.org/. This isn't a self-contained forum as the URL implies, but rather a web interface to a newsgroup server maintained by Digital Mars. If you ever find yourself wondering why you can't edit or delete posts in the D forums, this is why. The forum targeting new users is digitalmars.D.learn
, project and major news announcements are made in digitalmars.D.announce
, while digitalmars.D
is where you can go to witness or participate in discussions about the state of the language and its future direction. As you become more familiar with D and its ecosystem, some of the other forums might start to be of interest to you.
Note
The web interface called DFeed was developed in D by an active community member named Vladimir Panteleev. You can find the source for DFeed at https://github.com/CyberShadow/DFeed.
If the web interface doesn't do it for you, there are other options to access the forums. Given that the primary backend is a newsgroup, you can set up an account in a newsgroup reader for news.digitalmars.com
and select the newsgroups you're interested in following. Alternatively, you can point your browser at http://lists.puremagic.com/mailman/listinfo and subscribe to forums of interest via the mailing list interface. Again, the mailing lists are collectively an alternative interface to the newsgroups and not a completely independent entity.
Tip
The D community is generally helpful and friendly to those asking questions in the digitalmars.D.learn
forum. You should never feel hesitant about asking questions in the D forums. Experienced users drop by regularly, willing to answer the most basic questions. You can also find a number of D users idling in the #D
IRC channel. If you have an IRC client, #D
is located at http://freenode.net/. Anyone there can answer your questions about D. I've never been much of an IRC user, but I do drop by #D now and again. Whenever I'm around, I'll be happy to answer questions about this book or any of my other D projects. I'm usually found under the handle aldacron
in IRC and my real name in the forums.