Programming in D
By Ali Çehreli
()
About this ebook
The main aim of this book is to teach D to readers who are new to computer programming. Although having experience in other programming languages is certainly helpful, this book starts from the basics.
D is a multi-paradigm system programming language that combines a wide range of powerful programming concepts from the lowest to the highest levels. It has C-like syntax and static typing. It pragmatically combines efficiency, control, and modeling power, with safety and programmer productivity in mind.
Each chapter is based on the contents of the previous ones, introducing as few new concepts as possible. It is recommended that the book is read in linear fashion, without skipping chapters if possible.
Although this book was written with beginners in mind, it covers almost all features of D. More experienced programmers can use the book as a D language reference by starting from the index section.
Blurbs from the back cover:
“D is pristine, clean, immensely powerful, and arguably the actual state-of-the-art programming language. Ali's book is a gem. Clear, concise, and complete.” – Olivier Henley
“I have been using Ali’s online D book to teach D at the university level. It is up-to-date, complete, and most importantly, extremely readable. Having a print version is even better! This is now the 'go-to’ book for learning D programming.” – Chuck Allison, Professor and Chair, Computer Science Department, Utah Valley University
“Ali's explanations are succinct and on target. I like that he provides rationale for why D was designed in a particular way and how I can use it most effectively. This is the best computer language book I've read.” – Robbin Carlson, Luthier and Enterprise Architect
“I taught a CS2 Data Structures class in D with more success and student appreciation than when using either C++ or Java as it's an ideal language to express the relevant concepts at all scales, from detailed to big picture, without needless complexity.
Ali Çehreli's tutorial played a central role supporting students especially during the first half of the course — without it the course simply would not have worked, so "many thanks Ali" — and an important part of that is its linearity — it can be read with only backward dependencies. This meant that with hard work even students of little experience and only moderate current abilities could get up to speed, and we saw just that. It is hard to overstate this factor. I unreservedly recommend this book to all.” – Dr. Carl Sturtivant, University of Minnesota Department of Computer Science & Engineering
“This book is one of the best guides through the language that I've seen.” – Andrew Wray, D Enthusiast
“I encourage anyone considering D to read this book. Not exactly 'D for Dummies' but it's easy to follow even if you don't have much experience with compiled languages.” – bachmeier, Reddit user
“Having worked through the book, I have to say this is one of the easiest to follow and distraction free read there is and the fact that it made learning a new language a total breeze really impressed me.” – Imran Khan, Student
Related to Programming in D
Related ebooks
C language Programming: Simple, Short, and Straightforward Way of Learning C Programming Rating: 3 out of 5 stars3/5Begin & Master Programming with C Fundamentals Rating: 0 out of 5 stars0 ratingsProgramming Language Concepts: Improving your Software Development Skills Rating: 0 out of 5 stars0 ratingsD Web Development Rating: 0 out of 5 stars0 ratingsC Programming for Beginners: Your Guide to Easily Learn C Programming In 7 Days Rating: 4 out of 5 stars4/5C Programming For Beginners: The Simple Guide to Learning C Programming Language Fast! Rating: 5 out of 5 stars5/5C Programming Language The Beginner’s Guide Rating: 0 out of 5 stars0 ratingsLearn Kotlin for Android Development: The Next Generation Language for Modern Android Apps Programming Rating: 0 out of 5 stars0 ratingsGetting Started with Advanced C#: Upgrade Your Programming Skills Rating: 0 out of 5 stars0 ratingsDesign Patterns in C#: A Hands-on Guide with Real-world Examples Rating: 0 out of 5 stars0 ratingsExpert Delphi: Robust and fast cross-platform application development Rating: 0 out of 5 stars0 ratingsBeginning C: From Beginner to Pro Rating: 0 out of 5 stars0 ratingsDelphi High Performance: Build fast Delphi applications using concurrency, parallel programming and memory management Rating: 0 out of 5 stars0 ratingsC Language for Beginners with Easy Tips of C Basic Programming Rating: 0 out of 5 stars0 ratingsProgramming for Everyone Rating: 3 out of 5 stars3/5C# Programming Fundamentals Rating: 0 out of 5 stars0 ratingsHow to Learn PHP, MySQL and Javascript Quickly!: For Dummies Rating: 5 out of 5 stars5/5Introduction to programming in C, a practical approach. Rating: 0 out of 5 stars0 ratingsLearn Red – Fundamentals of Red: Get up and running with the Red language for full-stack development Rating: 0 out of 5 stars0 ratingsUnderstanding Python: Beginner's Guide to Programming Rating: 0 out of 5 stars0 ratingsSimple and Efficient Programming with C#: Skills to Build Applications with Visual Studio and .NET Rating: 0 out of 5 stars0 ratingsDeveloping Apps with Python and Flet Rating: 0 out of 5 stars0 ratingsProgramming Essentials Rating: 5 out of 5 stars5/5Delphi High Performance: Master the art of concurrency, parallel programming, and memory management to build fast Delphi apps Rating: 0 out of 5 stars0 ratingsThinking In C# Programming. Rating: 0 out of 5 stars0 ratingsSoftware Engineering from Scratch: A Comprehensive Introduction Using Scala Rating: 0 out of 5 stars0 ratingsProgramming for Kids and Beginners: 3-in-1 Masterclass into Python, Apps, and Games Rating: 0 out of 5 stars0 ratingsDeno Web Development: Write, test, maintain, and deploy JavaScript and TypeScript web applications using Deno Rating: 0 out of 5 stars0 ratingsAnsible for IT Experts Rating: 0 out of 5 stars0 ratings
Programming For You
SQL QuickStart Guide: The Simplified Beginner's Guide to Managing, Analyzing, and Manipulating Data With SQL Rating: 4 out of 5 stars4/5Coding All-in-One For Dummies Rating: 4 out of 5 stars4/5Grokking Algorithms: An illustrated guide for programmers and other curious people Rating: 4 out of 5 stars4/5Python Programming : How to Code Python Fast In Just 24 Hours With 7 Simple Steps Rating: 4 out of 5 stars4/5Teach Yourself C++ Rating: 4 out of 5 stars4/5Learn to Code. Get a Job. The Ultimate Guide to Learning and Getting Hired as a Developer. Rating: 5 out of 5 stars5/5C# Programming from Zero to Proficiency (Beginner): C# from Zero to Proficiency, #2 Rating: 0 out of 5 stars0 ratingsHTML & CSS: Learn the Fundaments in 7 Days Rating: 4 out of 5 stars4/5Excel : The Ultimate Comprehensive Step-By-Step Guide to the Basics of Excel Programming: 1 Rating: 5 out of 5 stars5/5Spies, Lies, and Algorithms: The History and Future of American Intelligence Rating: 4 out of 5 stars4/5Assembly Programming:Simple, Short, And Straightforward Way Of Learning Assembly Language Rating: 5 out of 5 stars5/5JavaScript All-in-One For Dummies Rating: 5 out of 5 stars5/5HTML in 30 Pages Rating: 5 out of 5 stars5/5Python QuickStart Guide: The Simplified Beginner's Guide to Python Programming Using Hands-On Projects and Real-World Applications Rating: 0 out of 5 stars0 ratingsLinux: Learn in 24 Hours Rating: 5 out of 5 stars5/5Programming Arduino: Getting Started with Sketches Rating: 4 out of 5 stars4/5Excel 101: A Beginner's & Intermediate's Guide for Mastering the Quintessence of Microsoft Excel (2010-2019 & 365) in no time! Rating: 0 out of 5 stars0 ratingsPYTHON: Practical Python Programming For Beginners & Experts With Hands-on Project Rating: 5 out of 5 stars5/5
Reviews for Programming in D
0 ratings0 reviews
Book preview
Programming in D - Ali Çehreli
Programming in D, First Edition
Revision: 2017-05-11
The most recent electronic versions of this book are available online.
Copyleft (ɔ) 2009-2017 Ali Çehreli
Creative Commons LicenseThis work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/.
Edited by Luís Marques
Cover design by İzgi Yapıcı
Cover illustration by Sarah Reece
Published by Ali Çehreli
Fonts:
Andada by Carolina Giovagnoli for Huerta Tipográfica
Open Sans by Steve Matteson
DejaVu Mono by DejaVu Fonts
PDF version is generated with Prince XML
Other ebook versions are generated with Calibre
ISBNs:
978-0-692-59943-3 hardcover by IngramSpark
978-0-692-52957-7 paperback by IngramSpark
978-1-515-07460-1 paperback by CreateSpace
978-1-519-95441-1 ePUB by Draft2Digital
Foreword by Andrei Alexandrescu
Those of us who know Ali might notice his book on D is imbued with its author's personality: straightforward, patient, and nice without being pandering.
There is purpose in every sentence, and with each, a step forward is being made; not too fast, and not too slow. Note that opApply() itself is implemented by a foreach loop. As a result, the foreach inside main() ends up making indirect use of a foreach over the points member.
And so it goes, in just as many words as needed. And in the right order, too; Ali does an admirable job at presenting language concepts – which especially to a beginner overwhelmingly come in parallel
– in a sequential manner.
But there's another thing I like most about Programming in D
: it's a good book for learning programming in general. See, a good introductory book on Haskell implicitly teaches functional programming along the way; one on C would come with systems programming notions in tow; one on Python with scripting, and so on. What would, then, a good introductory text to D teach in subtext? At best, Programming with a capital P.
D fosters a use the right tool for the job
attitude, and allows its user to tap into a wide range of programming techniques, without throwing too many idiosyncrasies in the way. The most fun way to approach coding in D is with an open mind, because for each design that starts to get stilted there is opportunity to mold it into the right design choosing a different implementation, approach, or paradigm altogether. To best choose what's most fitting, the engineer must know the gamut of what's possible – and Programming in D
is a great way to equip one's intellect with that knowledge. Internalizing it helps not only writing good code in D, but writing good code, period.
There's good tactical advice, too, to complement the teaching of programming and language concepts. Timeless teaching on avoiding code duplication, choosing good names, aiming for good decomposition, and more – it's all there, quick-and-dirty hacks iteratively annealed into robust solutions, just as they should in normal practice. Instead of falling for getting things done quickly, Programming in D
focuses on getting things done properly, to the lasting benefit of its reader.
I've long suspected D is a good first programming language to learn. It exposes its user to a variety of concepts – systems, functional, object oriented, generic, generative – candidly and without pretense. And so does Ali's book, which seems to me an excellent realization of that opportunity.
Andrei Alexandrescu
San Francisco, May 2015
Preface
D is a multi-paradigm system programming language that combines a wide range of powerful programming concepts from the lowest to the highest levels. It emphasizes memory safety, program correctness, and pragmatism.
The main aim of this book is to teach D to readers who are new to computer programming. Although having experience in other programming languages is certainly helpful, this book starts from the basics.
In order for this book to be useful, you will need an environment to write, compile, and run your D programs. This development environment must include at least a D compiler and a text editor. We will learn how to install a compiler and how to compile programs in the next chapter.
Each chapter is based on the contents of the previous ones, introducing as few new concepts as possible. I recommend that you read the book in linear fashion, without skipping chapters. Although this book was written with beginners in mind, it covers almost all features of D. More experienced programmers can use the book as a D language reference by starting from the index section.
Some chapters include exercises and their solutions so that you can write small programs and compare your methods to mine.
Computer programming is a satisfying craft that involves continuously discovering and learning new tools, techniques, and concepts. I am sure you will enjoy programming in D at least as much as I do. Learning to program is easier and more fun when shared with others. Take advantage of the D.learn newsgroup to follow discussions and to ask and answer questions.
This book is available in other languages as well, including Turkish and French.
Acknowledgments
I am indebted to the following people who have been instrumental during the evolution of this book:
Mert Ataol, Zafer Çelenk, Salih Dinçer, Can Alpay Çiftçi, Faruk Erdem Öncel, Muhammet Aydın (aka Mengü Kağan), Ergin Güney, Jordi Sayol, David Herberth, Andre Tampubolon, Gour-Gadadhara Dasa, Raphaël Jakse, Andrej Mitrović, Johannes Pfau, Jerome Sniatecki, Jason Adams, Ali H. Çalışkan, Paul Jurczak, Brian Rogoff, Михаил Страшун (Mihails Strasuns), Joseph Rushton Wakeling, Tove, Hugo Florentino, Satya Pothamsetti, Luís Marques, Christoph Wendler, Daniel Nielsen, Ketmar Dark, Pavel Lukin, Jonas Fiala, Norman Hardy, Rich Morin, Douglas Foster, Paul Robinson, Sean Garratt, Stéphane Goujet, Shammah Chancellor, Steven Schveighoffer, Robbin Carlson, Bubnenkov Dmitry Ivanovich, Bastiaan Veelo, Olivier Pisano, Dave Yost, Tomasz Miazek-Mioduszewski, Gerard Vreeswijk, Justin Whear, Gerald Jansen, Sylvain Gault, Shriramana Sharma, Jay Norwood, Henri Menke, Chen Lejia, Vladimir Panteleev, and Martin Tschierschke.
Thanks especially to Luís Marques who, through his hard work, improved every chapter of the book. If you find any part of this book useful, it is likely due to his diligent editing.
Thanks to Luís Marques, Steven Schveighoffer, Andrej Mitrović, Robbin Carlson, and Ergin Güney for their suggestions that elevated this book from my Inglish to English.
I am grateful to the entire D community for keeping my enthusiasm and motivation high. D has an amazing community of tireless individuals like bearophile and Kenji Hara.
Ebru, Damla, and Derin, thank you for being so patient and supportive while I was lost writing these chapters.
Ali Çehreli
Mountain View, May 2017
The Hello World Program
The first program to show in most programming language books is the hello world program. This very short and simple program merely writes hello world
and finishes. This program is important because it includes some of the essential concepts of that language.
Here is a hello world program in D:
import
std.stdio;
void
main() {
writeln(
Hello world!
);
}
The source code above needs to be compiled by a D compiler to produce an executable program.
Compiler installation
At the time of writing this chapter, there are three D compilers to choose from: dmd, the Digital Mars compiler; gdc, the D compiler of GCC; and ldc, the D compiler that targets the LLVM compiler infrastructure.
dmd is the D compiler that has been used during the design and development of the language over the years. All of the examples in this book have been tested with dmd. For that reason, it would be the easiest for you to start with dmd and try other compilers only if you have a specific need to. The code samples in this book were compiled with dmd version 2.074.0.
To install the latest version of dmd, go to the download page at Digital Mars and select the compiler build that matches your computer environment. You must select the dmd build that is for your operating system and package management system, and whether you have a 32-bit or a 64-bit CPU and operating system. Do not install a D1 compiler. This book covers only D version two.
The installation steps are different on different environments but it should be as easy as following simple on-screen instructions and clicking a couple of buttons.
Source file
The file that the programmer writes for the D compiler to compile is called the source file. Since D is usually used as a compiled language, the source file itself is not an executable program. The source file must be converted to an executable program by the compiler.
As with any file, the source file must have a name. Although the name can be anything that is legal on the file system, it is customary to use the .d file extension for D source files because development environments, programming tools, and programmers all expect this to be the case. For example, test.d, game.d, invoice.d, etc. are appropriate D source file names.
Compiling the hello world program
You will write the source file in a text editor (or an IDE as mentioned below). Copy or type the hello world program above into a text file and save it under the name hello.d.
The compiler will soon check that the syntax of this source code is correct (i.e. it is valid according to the language rules) and make a program out of it by translating it into machine code. Follow these steps to compile the program:
Open a terminal window.
Go to the directory where you saved hello.d.
Enter the following command. (Do not type the $ character; it is there to indicate the command line prompt.)
$ dmd hello.d
If you did not make any mistake, you may think that nothing has happened. To the contrary, it means that everything went well. There should be an executable file named hello (or hello.exe under Windows) that has just been created by the compiler.
If the compiler has instead printed some messages, you probably have made a mistake when copying the program code. Try to identify the mistake, correct it, and retry compiling. You will routinely make many mistakes when programming, so the process of correcting and compiling will become familiar to you.
Once the program has been created successfully, type the name of the executable program to run it. You should see that the program prints Hello world!
:
$ ./hello
← running the program
Hello world!
← the message that it prints
Congratulations! Your first D program works as expected.
Compiler switches
The compiler has many command line switches that are used for influencing how it compiles the program. To see a list of compiler switches enter just the name of the compiler:
$ dmd
← enter just the name
DMD64 D Compiler v2.074.0
Copyright (c) 1999-2017 by Digital Mars written by Walter Bright
...
-de show use of deprecated features as errors (halt compilation)
...
-unittest compile in unit tests
...
-w warnings as errors (compilation will halt)
...
The abbreviated output above shows only the command line switches that I recommend that you always use. Although it makes no difference with the hello world program in this chapter, the following command line would compile the program by enabling unit tests and not allowing any warnings or deprecated features. We will see these and other switches in more detail in later chapters:
$ dmd hello.d -de -w -unittest
The complete list of dmd command line switches can be found in the DMD Compiler documentation.
One other command line switch that you may find useful is -run. It compiles the source code, produces the executable program, and runs it with a single command. -run must be the last of compiler switches, specified right before the name of the source file:
$ dmd -de -w -unittest
-run
hello.d
Hello world!
← the program is automatically executed
IDE
In addition to the compiler, you may also consider installing an IDE (integrated development environment). IDEs are designed to make program development easier by simplifying the steps of writing, compiling, and debugging.
If you do install an IDE, compiling and running the program will be as simple as pressing a key or clicking a button on the IDE. I still recommend that you familiarize yourself with compiling programs manually in a terminal window.
If you decide to install an IDE, go to the IDEs page at dlang.org to see a list of available IDEs.
Contents of the hello world program
Here is a quick list of the many D concepts that have appeared in this short program:
Core feature: Every language defines its syntax, fundamental types, keywords, rules, etc. All of these make the core features of that language. The parentheses, semicolons, and words like main and void are all placed according to the rules of D. These are similar to the rules of English: subject, verb, punctuation, sentence structure, etc.
Library and function: The core features define only the structure of the language. They are used for defining functions and user types, and those in turn are used for building libraries. Libraries are collections of reusable program parts that get linked with your programs to help them achieve their purposes.
writeln above is a function in D's standard library. It is used for printing a line of text, as its name suggests: write line.
Module: Library contents are grouped by types of tasks that they intend to help with. Such a group is called a module. The only module that this program uses is std.stdio, which handles data input and output.
Character and string: Expressions like Hello world!
are called strings, and the elements of strings are called characters. The only string in this program contains characters 'H', 'e', '!', and others.
Order of operations: Programs complete their tasks by executing operations in a certain order. These tasks start with the operations that are written in the function named main. The only operation in this program writes Hello world!
.
Significance of uppercase and lowercase letters: You can choose to type any character inside strings, but you must type the other characters exactly as they appear in the program. This is because lowercase vs. uppercase is significant in D programs. For example, writeln and Writeln are two different names.
Keyword: Special words that are a part of the core features of the language are keywords. Such words are reserved for the language itself, and cannot be used for any other purpose in a D program. There are two keywords in this program: import, which is used to introduce a module to the program; and void, which here means not returning anything
.
The complete list of D keywords is abstract, alias, align, asm, assert, auto, body, bool, break, byte, case, cast, catch, cdouble, cent, cfloat, char, class, const, continue, creal, dchar, debug, default, delegate, delete, deprecated, do, double, else, enum, export, extern, false, final, finally, float, for, foreach, foreach_reverse, function, goto, idouble, if, ifloat, immutable, import, in, inout, int, interface, invariant, ireal, is, lazy, long, macro, mixin, module, new, nothrow, null, out, override, package, pragma, private, protected, public, pure, real, ref, return, scope, shared, short, static, struct, super, switch, synchronized, template, this, throw, true, try, typedef, typeid, typeof, ubyte, ucent, uint, ulong, union, unittest, ushort, version, void, volatile, wchar, while, with, __FILE__, __FILE_FULL_PATH__, __MODULE__, __LINE__, __FUNCTION__, __PRETTY_FUNCTION__, __gshared, __traits, __vector, and __parameters.
We will cover these keywords in the upcoming chapters with the exception of the following ones: asm and __vector are outside of the scope of this book; delete, typedef, and volatile are deprecated; and macro is unused by D at this time.
Exercises
Make the program output something else.
Change the program to output more than one line. You can do this by adding one more writeln line to the program.
Try to compile the program after making other changes; e.g. remove the semicolon at the end of the line with writeln and observe a compilation error.
... the solutions
writeln and write
In the previous chapter we have seen that writeln takes a string within parentheses and prints the string.
The parts of programs that actually do work are called functions and the information that they need to complete their work are called parameters. The act of giving such information to functions is called passing parameter values to them. Parameters are passed to functions within parentheses, separated by commas.
Note: The word parameter describes the information that is passed to a function at the conceptual level. The concrete information that is actually passed during the execution of the program is called an argument. Although not technically the same, these terms are sometimes used interchangably in the software industry.
writeln can take more than one argument. It prints them one after the other on the same line:
import
std.stdio;
void
main() {
writeln(
Hello world!
, Hello fish!
);
}
Sometimes, all of the information that is to be printed on the same line may not be readily available to be passed to writeln. In such cases, the first parts of the line may be printed by write and the last part of the line may be printed by writeln.
writeln advances to the next line, write stays on the same line:
import
std.stdio;
void
main() {
// Let's first print what we have available: write(Hello
);
// ... let's assume more operations at this point ...
write(
world!
);
// ... and finally:
writeln();
}
Calling writeln without any parameter merely completes the current line, or if nothing has been written, outputs a blank line.
Lines that start with // are called comment lines or briefly comments. A comment is not a part of the program code in the sense that it doesn't affect the behavior of the program. Its only purpose is to explain what the code does in that particular section of the program. The audience of a comment is anybody who may be reading the program code later, including the programmer who wrote the comment in the first place.
Exercises
Both of the programs in this chapter print the strings without any spaces between them. Change the programs so that there is space between the arguments as in Hello world!
.
Try calling write with more than one parameter as well.
... the solutions
Compilation
We have seen that the two tools that are used most in D programming are the text editor and the compiler. D programs are written in text editors.
The concept of compilation and the function of the compiler must also be understood when using compiled languages like D.
Machine code
The brain of the computer is the microprocessor (or the CPU, short for central processing unit). Telling the CPU what to do is called coding, and the instructions that are used when doing so are called machine code.
Most CPU architectures use machine code specific to that particular architecture. These machine code instructions are determined under hardware constraints during the design stage of the architecture. At the lowest level these machine code instructions are implemented as electrical signals. Because the ease of coding is not a primary consideration at this level, writing programs directly in the form of the machine code of the CPU is a very difficult task.
These machine code instructions are special numbers, which represent various operations supported by the CPU. For example, for an imaginary 8-bit CPU, the number 4 might represent the operation of loading, the number 5 might represent the operation of storing, and the number 6 might represent the operation of incrementing. Assuming that the leftmost 3 bits are the operation number and the rightmost 5 bits are the value that is used in that operation, a sample program in machine code for this CPU might look like the following:
Operation Value Meaning
100 11110 LOAD 11110
101 10100 STORE 10100
110 10100 INCREMENT 10100
000 00000 PAUSE
Being so close to hardware, machine code is not suitable for representing higher level concepts like a playing card or a student record.
Programming language
Programming languages are designed as efficient ways of programming a CPU, capable of representing higher-level concepts. Programming languages do not have to deal with hardware constraints; their main purposes are ease of use and expressiveness. Programming languages are easier for humans to understand, closer to natural languages:
if (a_card_has_been_played()) {
display_the_card();
}
However, programming languages adhere to much more strict and formal rules than any spoken language.
Interpreter
An interpreter is a tool (a program) that reads the instructions from source code and executes them. For example, for the code above, an interpreter would understand to first execute a_card_has_been_played() and then conditionally execute display_the_card(). From the point of view of the programmer, executing with an interpreter involves just two steps: writing the source code and giving it to the interpreter.
The interpreter must read and understand the instructions every time the program is executed. For that reason, running a program with an interpreter is usually slower than running the compiled version of the same program. Additionally, interpreters usually perform very little analysis on the code before executing it. As a result, most interpreters discover programming errors only after they start executing the program.
Some languages like Perl, Python and Ruby have been designed to be very flexible and dynamic, making code analysis harder. These languages have traditionally been used with an interpreter.
Compiler
A compiler is another tool that reads the instructions of a program from source code. Different from an interpreter, it does not execute the code; rather, it produces a program written in another language (usually machine code). This produced program is responsible for the execution of the instructions that were written by the programmer. From the point of view of the programmer, executing with a compiler involves three steps: writing the source code, compiling it, and running the produced program.
Unlike an interpreter, the compiler reads and understands the source code only once, during compilation. For that reason and in general, a compiled program runs faster compared to executing that program with an interpreter. Compilers usually perform advanced analysis on the code, which help with producing fast programs and catching programming errors before the program even starts running. On the other hand, having to compile the program every time it is changed is a complication and a potential source of human errors. Moreover, the programs that are produced by a compiler can usually run only on a specific platform; to run on a different kind of processor or on a different operating system, the program would have to be recompiled. Additionally, the languages that are easy to compile are usually less dynamic than those that run in an interpreter.
For reasons like safety and performance, some languages have been designed to be compiled. Ada, C, C++, and D are some of them.
Compilation error
As the compiler compiles a program according to the rules of the language, it stops the compilation as soon as it comes across illegal instructions. Illegal instructions are the ones that are outside the specifications of the language. Problems like a mismatched parenthesis, a missing semicolon, a misspelled keyword, etc. all cause compilation errors.
The compiler may also emit a compilation warning when it sees a suspicious piece of code that may cause concern but not necessarily an error. However, warnings almost always indicate an actual error or bad style, so it is a common practice to consider most or all warnings as errors. The dmd compiler switch to enable warnings as errors is -w.
Fundamental Types
We have seen that the brain of a computer is the CPU. Most of the tasks of a program are performed by the CPU and the rest are dispatched to other parts of the computer.
The smallest unit of data in a computer is called a bit. The value of a bit can be either 0 or 1.
Since a type of data that can hold only the values 0 and 1 would have very limited use, the CPU supports larger data types that are combinations of more than one bit. As an example, a byte usually consists of 8 bits. If an N-bit data type is the most efficient data type supported by a CPU, we consider it to be an N-bit CPU: as in 32-bit CPU, 64-bit CPU, etc.
The data types that the CPU supports are still not sufficient: they can't represent higher level concepts like name of a student or a playing card. Likewise, D's fundamental data types are not sufficient to represent many higher level concepts. Such concepts must be defined by the programmer as structs and classes, which we will see in later chapters.
D's fundamental types are very similar to the fundamental types of many other languages, as seen in the following table. The terms that appear in the table are explained below:
In addition to the above, the keyword void represents having no type. The keywords cent and ucent are reserved for future use to represent signed and unsigned 128 bit values.
Unless there is a specific reason not to, you can use int to represent whole values. To represent concepts that can have fractional values, consider double.
The following are the terms that appeared in the table:
Boolean: The type of logical expressions, having the value true for truth and false for falsity.
Signed type: A type that can have negative and positive values. For example, byte can have values from -128 to 127. The names of these types come from the negative sign.
Unsigned type: A type that can have only positive values. For example, ubyte can have values from 0 to 255. The u at the beginning of the name of these types comes from unsigned.
Floating point: The type that can represent values with fractions as in 1.25. The precision of floating point calculations are directly related to the bit count of the type: higher the bit count, more precise the results are. Only floating point types can represent fractions; integer types like int can only represent whole values like 1 and 2.
Complex number type: The type that can represent the complex numbers of mathematics.
Imaginary number type: The type that represents only the imaginary part of complex numbers. The i that appears in the Initial Value column is the square root of -1 in mathematics.
nan: Short for not a number
, representing invalid floating point value.
Properties of types
D types have properties. Properties are accessed with a dot after the name of the type. For example, the sizeof property of int is accessed as int.sizeof. We will see only some of type properties in this chapter:
.stringof is the name of the type
.sizeof is the length of the type in terms of bytes. (In order to determine the bit count, this value must be multiplied by 8, the number of bits in a byte.)
.min is short for minimum
; this is the smallest value that the type can have
.max is short for maximum
; this is the largest value that the type can have
.init is short for initial value
(default value); this is the value that D assigns to a type when an initial value is not specified
Here is a program that prints these properties for int:
import
std.stdio;
void
main() {
writeln(
Type :
, int
.stringof);
writeln(
Length in bytes:
, int
.sizeof);
writeln(
Minimum value :
, int
.min);
writeln(
Maximum value :
, int
.max);
writeln(
Initial value :
, int
.init);
}
The output of the program is the following:
Type : int
Length in bytes: 4
Minimum value : -2147483648
Maximum value : 2147483647
Initial value : 0
size_t
You will come across the size_t type as well. size_t is not a separate type but an alias of an existing unsigned type. Its name comes from size type
. It is the most suitable type to represent concepts like size or count.
size_t is large enough to represent the number of bytes of the memory that a program can potentially be using. Its actual size depends on the system: uint on a 32-bit system and ulong on a 64-bit system. For that reason, ulong is larger than size_t on a 32-bit system.
You can use the .stringof property to see what size_t is an alias of on your system:
import
std.stdio;
void
main() {
writeln(size_t.stringof);
}
The output of the program is the following on my system:
ulong
Exercise
Print the properties of other types.
Note: You can't use the reserved types cent and ucent in any program; and as an exception, void does not have the properties .min, .max and .init.
Additionally, the .min property is deprecated for floating point types. (You can see all the various properties for the fundamental types in the D property specification). If you use a floating point type in this exercise, you would be warned by the compiler that .min is not valid for that type. Instead, as we will see later in the Floating Point Types chapter, you must use the negative of the .max property e.g. as -double.max.
... the solution
Assignment and Order of Evaluation
The first two difficulties that most students face when learning to program involve the assignment operation and the order of evaluation.
The assignment operation
You will see lines similar to the following in almost every program in almost every programming language:
a = 10;
The meaning of that line is make a's value become 10
. Similarly, the following line means make b's value become 20
:
b = 20;
Based on that information, what can be said about the following line?
a = b;
Unfortunately, that line is not about the equality concept of mathematics that we all know. The expression above does not mean a is equal to b
! When we apply the same logic from the earlier two lines, the expression above must mean make a's value become the same as b's value
.
The well-known = symbol of mathematics has a completely different meaning in programming: make the left side's value the same as the right side's value.
Order of evaluation
In general, the operations of a program are applied step by step in the order that they appear in the program. (There are exceptions to this rule, which we will see in later chapters.) We may see the previous three expressions in a program in the following order:
a = 10;
b = 20;
a = b;
The meaning of those three lines altogether is this: "make a's value become 10, then make b's value become 20, then make a's value become the same as b's value". Accordingly, after those three operations are performed, the value of both a and b would be 20.
Exercise
Observe that the following three operations swap the values of a and b. If at the beginning their values are 1 and 2 respectively, after the operations the values become 2 and 1:
c = a;
a = b;
b = c;
... the solution
Variables
Concrete concepts that are represented in a program are called variables. A value like air temperature and a more complicated object like a car engine can be variables of a program.
The main purpose of a variable is to represent a value in the program. The value of a variable is the last value that has been assigned to that variable. Since every value is of a certain type, every variable is of a certain type as well. Most variables have names as well, but some variables are anonymous.
As an example of a variable, we can think of the concept of the number of students at a school. Since the number of students is a whole number, int is a suitable type, and studentCount would be a sufficiently descriptive name.
According to D's syntax rules, a variable is introduced by its type followed by its name. The introduction of a variable to the program is called its definition. Once a variable is defined, its name represents its value.
import
std.stdio;
void
main() {
// The definition of the variable; this definition // specifies that the type of studentCount is int: int
studentCount;
// The name of the variable becomes its value: writeln(There are
, studentCount, students.
);
}
The output of this program is the following:
There are 0 students.
As seen from that output, the value of studentCount is 0. This is according to the fundamental types table from the previous chapter: the initial value of int is 0.
Note that studentCount does not appear in the output as its name. In other words, the output of the program is not There are studentCount students
.
The values of variables are changed by the = operator. The = operator assigns new values to variables, and for that reason is called the assignment operator:
import
std.stdio;
void
main() {
int
studentCount;
writeln(
There are
, studentCount, students.
);
// Assigning the value 200 to the studentCount variable: studentCount
=
200;
writeln(
There are now
, studentCount, students.
);
}
There are 0 students.
There are now 200 students.
When the value of a variable is known at the time of the variable's definition, the variable can be defined and assigned at the same time. This is an important guideline; it makes it impossible to use a variable before assigning its intended value:
import
std.stdio;
void
main() {
// Definition and assignment at the same time: int
studentCount = 100;
writeln(
There are
, studentCount, students.
);
}
There are 100 students.
Exercise
Define two variables to print I have exchanged 20 Euros at the rate of 2.11
. You can use the floating point type double for the decimal value.
... the solution
Standard Input and Output Streams
So far, the printed output of our programs has been appearing on the terminal window (or screen). Although the terminal is often the ultimate target of output, this is not always the case. The objects that can accept output are called standard output streams.
The standard output is character based; everything to be printed is first converted to the corresponding character representation and then sent to the output as characters. For example, the integer value 100 that we've printed in the last chapter is not sent to the output as the value 100, but as the three characters 1, 0, and 0.
Similarly, what we normally perceive as the keyboard is actually the standard input stream of a program and is also character based. The information always comes as characters to be converted to data. For example, the integer value 42 actually comes through the standard input as the characters 4 and 2.
These conversions happen automatically.
This concept of consecutive characters is called a character stream. As D's standard input and standard output fit this description, they are character streams.
The names of the standard input and output streams in D are stdin and stdout, respectively.
Operations on these streams normally require the name of the stream, a dot, and the operation; as in stream.operation(). Because stdin's reading methods and stdout's writing methods are used very commonly, those operations can be called without the need of the stream name and the dot.
writeln that we've been using in the previous chapters is actually short for stdout.writeln. Similarly, write is short for stdout.write. Accordingly, the hello world program can also be written as follows:
import
std.stdio;
void
main() {
stdout.
writeln(Hello world!
);
}
Exercise
Observe that stdout.write works the same as write.
... the solution
Reading from the Standard Input
Any data that is read by the program must first be stored in a variable. For example, a program that reads the number of students from the input must store this information in a variable. The type of this specific variable can be int.
As we've seen in the previous chapter, we don't need to type stdout when printing to the output, because it is implied. Further, what is to be printed is specified as the argument. So, write(studentCount) is sufficient to print the value of studentCount. To summarize:
stream: stdout
operation: write
data: the value of the studentCount variable
target: commonly the terminal window
The reverse of write is readf; it reads from the standard input. The f
in its name comes from formatted
as what it reads must always be presented in a certain format.
We've also seen in the previous chapter that the standard input stream is stdin.
In the case of reading, one piece of the puzzle is still missing: where to store the data. To summarize:
stream: stdin
operation: readf
data: some information
target: ?
The location of where to store the data is specified by the address of a variable. The address of a variable is the exact location in the computer's memory where its value is stored.
In D, the & character that is typed before a name is the address of what that name represents. For example, the address of studentCount is &studentCount. Here, &studentCount can be read as the address of studentCount
and is the missing piece to replace the question mark above:
stream: stdin
operation: readf
data: some information
target: the location of the studentCount variable
Typing a & in front of a name means pointing at what that name represents. This concept is the foundation of references and pointers that we will see in later chapters.
I will leave one peculiarity about the use of readf for later; for now, let's accept as a rule that the first argument to readf must be %s
:
readf(%s
, &studentCount);
Note: As I explain below, in most cases there must also be a space: %s
.
Actually, readf can work without the & character as well:
readf(%s
, studentCount); // same as above
Although the code is cleaner and safer without the & character, I will continue to use readf with pointers partly to prepare you to the concepts of references and reference function parameters.
%s
indicates that the data should automatically be converted in a way that is suitable to the type of the variable. For example, when the '4' and '2' characters are read to a variable of type int, they are converted to the integer value 42.
The program below asks the user to enter the number of students. You must press the Enter key after typing the input:
import
std.stdio;
void
main() {
write(
How many students are there?
);
/* The definition of the variable that will be used to
* store the information that is read from the input. */
int
studentCount;
// Storing the input data to that variable readf(%s
, &studentCount);
writeln(
Got it: There are
, studentCount, students.
);
}
Skipping the whitespace characters
Even the Enter key that we press after typing the data is stored as a special code and is placed into the stdin stream. This is useful to the programs to detect whether the information has been input on a single line or multiple lines.
Although sometimes useful, such special codes are mostly not important for the program and must be filtered out from the input. Otherwise they block the input and prevent reading other data.
To see this problem in a program, let's also read the number of teachers from the input:
import
std.stdio;
void
main() {
write(
How many students are there?
);
int
studentCount;
readf(
%s
, &studentCount);
write(
How many teachers are there?
);
int
teacherCount;
readf(
%s
, &teacherCount);
writeln(
Got it: There are
, studentCount, students
,
and
, teacherCount, teachers.
);
}
Unfortunately, the program cannot use that special code when expecting an int value:
How many students are there? 100
How many teachers are there? 20
← An exception is thrown here
Although the user enters the number of teachers as 20, the special code(s) that represents the Enter key that has been pressed when entering the previous 100 is still in the input stream and is blocking it. The characters that appeared in the input stream are similar to the following representation:
100
[EnterCode]
20[EnterCode]
I have highlighted the Enter code that is blocking the input.
The solution is to use a space character before %s to indicate that the Enter code that appears before reading the number of teachers is not important: %s
. Spaces that are in the format strings are used to read and ignore zero or more invisible characters that would otherwise appear in the input. Such characters include the actual space character, the code(s) that represent the Enter key, the Tab character, etc. and are called the whitespace characters.
As a general rule, you can use %s
for every data that is read from the input. The program above works as expected with the following changes:
// ... readf( %s
, &studentCount); // ... readf( %s
, &teacherCount); // ...
The output:
How many students are there? 100
How many teachers are there? 20
Got it: There are 100 students and 20 teachers.
Additional information
Lines that start with // are useful for single lines of comments. To write multiple lines as a single comment, enclose the lines within /* and */ markers.
In order to be able to comment even other comments, use /+ and +/:
/+
// A single line of comment
/*
A comment that spans
multiple lines
*/
/+
It can even include nested /+ comments +/
+/
A comment block that includes other comments
+/
Most of the whitespace in the source code is insignificant. It is good practice to write longer expressions as multiple lines or add extra whitespace to make the code more readable. Still, as long as the syntax rules of the language are observed, the programs can be written without any extra whitespace:
import std.stdio;void main(){writeln(Hard to read!
);}
It can be hard to read source code with small amounts of whitespace.
Exercise
Enter non-numerical characters when the program is expecting integer values and observe that the program does not work correctly.
... the solution
Logical Expressions
The actual work that a program performs is accomplished by expressions. Any part of a program that produces a value or a side effect is called an expression. It has a very wide definition because even a constant value like 42 and a string like hello
are expressions, since they produce the respective constant values 42 and hello
.
Note: Don't confuse producing a value with defining a variable. Values need not be associated with variables.
Function calls like writeln are expressions as well because they have side effects. In the case of writeln, the effect is on the output stream by the placement of characters on it. Another example from the programs that we have written so far would be the assignment operation, which affects the variable that is on its left-hand side.
Because of producing values, expressions can take part in other expressions. This allows us to form more complex expressions from simpler ones. For example, assuming that there is a function named currentTemperature that produces the value of the current air temperature, the value that it produces may directly be used in a writeln expression:
writeln(It's
, currentTemperature(),
degrees at the moment.
);
That line consists of four expressions:
It's
currentTemperature()
degrees at the moment.
The writeln() expression that makes use of the other three
In this chapter we will cover the particular type of expression that is used in conditional statements.
Before going further though, I would like to repeat the assignment operator once more, this time emphasizing the two expressions that appear on its left and right sides: the assignment operator (=) assigns the value of the expression on its right-hand side to the expression on its left-hand side (e.g. to a variable).
temperature
=
23 // temperature's value becomes 23
Logical Expressions
Logical expressions are the expressions that are used in Boolean arithmetic. Logical expressions are what makes computer programs make decisions like if the answer is yes, I will save the file
.
Logical expressions can have one of only two values: false that indicates falsity, and true that indicates truth.
I will use writeln expressions in the following examples. If a line has true printed at the end, it will mean that what is printed on the line is true. Similarly, false will mean that what is on the line is false. For example, if the output of a program is the following,
There is coffee: true
then it will mean that there is coffee
. Similarly,
There is coffee: false
will mean that there isn't coffee
. Note that the fact that is
appears on the left-hand side does not mean that coffee exists. I use the ... is ...: false
construct to mean is not
or is false
.
Logical expressions are used extensively in conditional statements, loops, function parameters, etc. It is essential to understand how they work. Luckily, logical expressions are easy to explain and use.
The logical operators that are used in logical expressions are the following:
The == operator answers the question is equal to?
. It compares the two expressions on its left and right sides and produces true if they are equal and false if they are not. By definition, the value that == produces is a logical expression.
As an example, let's assume that we have the following two variables:
int
daysInWeek = 7;
int monthsInYear = 12;
The following are two logical expressions that use those values:
daysInWeek == 7 // true monthsInYear == 11 // false
The != operator answers the question is not equal to?
. It compares the two expressions on its sides and produces the opposite of ==.
daysInWeek != 7 // false monthsInYear != 11 // true
The || operator means or
, and produces true if any one of the logical expressions is true.
If the value of the left-hand expression is true, it produces true without even looking at the expression that is on the right-hand side. If the left-hand side is false, then it produces the value of the right-hand side. This operator is similar to the or
in English: if the left one, the right one, or both are true, then it produces true.
The following table presents all of the possible values for both sides of this operator and its result:
import
std.stdio;
void
main() {
// false means no
, true means yes
bool existsCoffee = false
;
bool existsTea = true
;
writeln(
There is warm drink:
,
existsCoffee
||
existsTea);
}
Because at least one of the two expressions is true, the logical expression above produces true.
The && operator means and
, and produces true if both of the expressions are true.
If the value of the left-hand expression is false, it produces false without even looking at the expression that is on the right-hand side. If the left-hand side is true, then it produces the value of the right-hand side. This operator is similar to the and
in English: if the left value and the right value are true, then it produces true.
writeln(I will drink coffee:
,
wantToDrinkCoffee
&&
existsCoffee);
Note: The fact that the || and && operators may not evaluate the right-hand expression is called their short-circuit behavior . The ternary operator ?:, which we will see in a later chapter, is similar in that it never evaluates one of its three expressions. All of the other operators always evaluate and use all of their expressions.
The ^ operator answers the question is one or the other but not both?
. This operator produces true if only one expression is true, but not both.
Warning: In reality, this operator is not a logical operator but an arithmetic one. It behaves like a logical operator only if both of the expressions are bool.
For example, the logic that represents my playing chess if only one of my two friends shows up can be coded like this:
writeln(I will play chess:
, jimShowedUp
^
bobShowedUp);
The < operator answers the question is less than?
(or does come before in sort order?
).
writeln(We beat:
, theirScore
<
ourScore);
The > operator answers the question is greater than?
(or does come after in sort order?
).
writeln(They beat:
, theirScore
>
ourScore);
The <= operator answers the question is less than or equal to?
(or does come before or the same in sort order?
). This operator is the opposite of the > operator.
writeln(We were not beaten:
, theirScore
<=
ourScore);
The >= operator answers the question is greater than or equal to?
(or does come after or the same in sort order?
). This operator is the opposite of the < operator.
writeln(We did not beat:
, theirScore
>=
ourScore);
The ! operator means the opposite of
. Different from the other logical operators, it takes just one expression and produces true if that expression is false, and false if that expression is true.
writeln(I will walk:
,
!
existsBicycle);
Grouping expressions
The order in which the expressions are evaluated can be specified by using parentheses to group them. When parenthesized expressions appear in more complex expressions, the parenthesized expressions are evaluated before they can be used in the expressions that they appear in. For example, the expression if there is coffee or tea, and also cookie or scone; then I am happy
can be coded like the following:
writeln(I am happy:
,
(existsCoffee || existsTea) && (existsCookie || existsScone));
If the sub expressions were not parenthesized, the expressions would be evaluated according to operator precedence rules of D (which have been inherited from the C language). Since in these rules && has a higher precedence than ||, writing the expression without parentheses would not be evaluated as intended:
writeln(I am happy:
,
existsCoffee || existsTea && existsCookie || existsScone);
The && operator would be evaluated first and the whole expression would be the semantic equivalent of the following expression:
writeln(I am happy:
,
existsCoffee || (existsTea && existsCookie) || existsScone);
That has a totally different meaning: if there is coffee, or tea and cookie, or scone; then I am happy
.
The operator precedence table will be presented later in the book.
Reading bool input
All of the bool values above are automatically printed as false
or true
. It is the same in the opposite direction: readf() automatically converts strings false
and true
to bool values false and true, respectively. It accepts any combination of lower and uppercase letters as well. For example, False
and FALSE
are converted to false and True
and TRUE
are converted to true.
Exercises
We've seen above that the < and the > operators are used to determine whether a value is less than or greater than another value; but there is no operator that answers the question is between?
to determine whether a value is between two other values.
Let's assume that a programmer has written the following code to determine whether value is between 10 and 20. Observe that the program cannot be compiled as written:
import
std.stdio;
void
main() {
int
value = 15;
writeln(
Is between:
,
10 < value < 20);
// ←
compilation ERROR
}
Try using parentheses around the whole expression:
writeln(Is between:
,
(10 < value < 20));
// ←
compilation ERROR
Observe that it still cannot be compiled.
While searching for a solution to this problem, the same programmer discovers that the following use of parentheses now enables the code to be compiled:
writeln(Is between:
,
(10 < value) < 20);
// ← compiles but WRONG
Observe that the program now works as expected and prints true
. Unfortunately, that output is misleading because the program has a bug. To see the effect of that bug, replace 15 with a value greater than 20:
int value = 21;
Observe that the program still prints true
even though 21 is not less than 20.
Hint: Remember that the type of a logical expression is bool. It shouldn't make sense whether a bool value is less than 20. The reason it compiles is due to the compiler converting the boolean expression to a 1 or 0, and then evaluating that against 20 to see if it is less.
The logical expression that answers the question is between?
must instead be coded like this: is greater than the lower value and less than the upper value?
.
Change the expression in the program according to that logic and observe that it now prints true
as expected. Additionally, test that the logical expression works correctly for other values as well: for example, when value is 50 or 1, the program should print false
; and when it is 12, the program should print true
.
Let's assume that we can go to the beach when one of the following conditions is true:
If the distance to the beach is less than 10 miles and there is a bicycle for everyone
If there is fewer than 6 of us, and we have a car, and at least one of us has a driver license
As written, the following program always prints true
. Construct a logical expression that will print true
when one of the conditions above is true. (When trying the program, enter false
or true
for questions that start with Is there a
.).
import std.stdio; import std.conv; import
std.string;
void
main() {
write(
How many are we?
);
int
personCount;
readf(
%s
, &personCount);
write(
How many bicycles are there?
);
int
bicycleCount;
readf(
%s
, &bicycleCount);
write(
What is the distance to the beach?
);
int
distance;
readf(
%s
, &distance);
write(
Is there a car?
);
bool
existsCar;
readf(
%s
, &existsCar);
write(
Is there a driver license?
);
bool
existsLicense;
readf(
%s
, &existsLicense);
/* Replace the 'true' below with a logical expression that
* produces the value 'true' when one of the conditions
* listed in the question is satisfied: */
writeln(
We are going to the beach:
, true
);
}
Enter various values and test that the logical expression that you wrote works correctly.
... the solutions
if Statement
We've learned that the actual work in a program is performed by expressions. All of the expressions of all of the programs that we've seen so far have started with the main() function and were executed until the end of main.
Statements, on the other hand, are features that affect the execution of expressions. Statements don't produce values and don't have side effects themselves. They determine whether and in what order the expressions are executed. Statements sometimes use logical expressions when making such decisions.
Note: Other programming languages may have different definitions for expression and statement, while some others may not have a distinction at all.
The if block and its scope
The if statement determines whether one or more expressions would be executed. It makes this decision by evaluating a logical expression. It has the same meaning as the English word if
, as in the phrase if there is coffee then I will drink coffee
.
if takes a logical expression in parentheses. If the value of that logical expression is true, then it executes the expressions that are within the following curly brackets. Conversely, if the logical expression is false, it does not execute the expressions within the curly brackets.
The area within the curly brackets is called a scope and all of the code that is in that scope is called a block of code.
Here is the syntax of the if statement:
if
(a_logical_expression) {
// ... expression(s) to execute if true }
For example, the program construct that represents if there is coffee then drink coffee and wash the cup
can be written as in the following program:
import
std.stdio;
void
main() {
bool existsCoffee = true
;
if
(existsCoffee) {
writeln(
Drink coffee
);
writeln(
Wash the cup
);
}
}
If the value of existsCoffee is false, then the expressions that are within the block would be skipped and the program would not print anything.