As I’m sure many others do, I often find myself writing short programs and utilities to help automate whatever random crap I’m doing — or just for fun. “Traditional” choices for this kind of work are usually scripting languages like Python, Ruby, or even more traditionally, Perl. What makes these languages good choices for banging stuff out? Most importantly, these languages are expressive. They let you Get Things Done™ with a minimal amount of effort. This usually comes down to convenient language features and a simple syntax that stays out of your way. These languages also make life easy by having many libraries — both standard and community-provided — that give you powerful tools to Get Things Done™. A package manager like Python’s pip or RubyGems is really nice to have, since it lets you discover libraries for whatever it is you’re trying to do.

Discovering D

I finally got around to learning Python and was starting to use it for side projects when I ran into D. Curiosity quickly gave way to excitement as I found that using D provides all the ease I had come to expect from Python on top of features I haven’t seen anywhere else. Let’s take a look:

Convenience

D abounds with syntactic sugar and features to make your life easier, including:

  • Built-in resizing arrays and associative arrays

    auto myMap = ["the answer" : 42, "revolution" : 9, "swallows per coconut" : 2];
    writeln(myMap["revolution"]); // Prints 9
    auto myArray = [25, 6, 2, 4];
    myArray ~= 3; // Append 3
    writeln([myArray[4]); // Prints 3
    
  • Array slicing

    auto sliceMe = [25, 6, 2, 4];
    auto theSlice = sliceMe[2 .. $]; // $ is the array length
    writeln(theSlice); // Prints [2, 4]
    
  • Foreach loops (now with indices!)

    auto words = ["lime", "coconut"];
    // Prints each word
    foreach (word; words) {
        writeln(word);
    }
    
    // Number the words.
    // num is provided the index and word is provided the value for each iteration.
    foreach (num, word; words) {
        writeln(num, ": ", word);
    }
    
  • Properties

    Getters and setters, while great for encapsulation, are a pain in the ass to write and add a bunch of boilerplate. C# has the awesome idea of “properties”, where you can define getter and setter functions that are automagically called whenever you get or set the property’s value. D offers the same:

    auto myArray = [1, 2, 3, 4, 5];
    writeln(myArray.length); // Length is a property
    myArray.length = 3; // Setting the property resizes the array
    
    auto someFile = File("someText.txt", "r");
    // byLine is a property of File that reads text line by line
    foreach (line; file.byLine) {
        writeln(line);
    }
    
  • Type inference and compile-time checks

    I was always horrified by the idea that in Python, misspelling a variable name or using the wrong type causes problems as my program runs instead of being caught beforehand.

    You come to me at runtime to tell me the code you are executing does not compile

    D is compiled and statically typed, but its auto keyword allows for types to be deduced by the compiler. Enjoy all the benefits of compile-time type checking without the traditional hassle associated with it.

    auto a = 42; // a is now an int
    a = "not an int"; // compile-time error
    
    auto foo() { return 10; } // foo returns an int
    auto bar() { return 25.64; } // bar returns a double
    auto baz() { return foo() + bar(); } // baz returns a double
    
    // Works for any types T1 and T2 that can be compared with <
    // and issues a nice compilation error for types that cannot
    auto min(T1, T2)(T1 lhs, T2 rhs)
    {
        return lhs < rhs ? lhs : rhs;
    }
    
  • Universal Function Call Syntax (UFCS)

    Similar to C#’s extension methods, D allows you to add functionality to types without modifying their actual implementation. If D encounters an expression in the form variable.foo(args) and foo is not a member of variable, D tries again after reforming the expression to foo(variable, args). This sounds odd at first, but is quite useful. Take, for example, the to function, which can convert between types. It is a standalone function, but instead of to!string(someInt), you can type the much more natural someInt.to!string. It also makes chaining algorithms very simple. The following is a complete program taken from Andrei Alexandrescu’s “Leverage” talk that reads in a list of floating-point values, ignoring empty lines and commented ones starting with #, sorts them, and prints out the first ten:

    import std.algorithm, std.stdio, std.range, std.conv;
    void main()
    {
        stdin
            .byLine
            .filter!(s => !s.empty && s.front != '#') // Filter with this lambda function
            .map!(s => s.to!double) // Map the strings to doubles
            .array // Sorting needs random access
            .sort!((a, b) => a < b) // Another lambda
            .take(10) // Applyable to any range
            .writeln;
    }
    

    Each one of those functions is completely free-standing, but much like Unix utilities, they can be easily chained together to get lots of work done.

  • Concurrency support

    As modern hardware trends continue to favor multi-core designs due to the increased difficulty in improving per-core clock speeds, concurrency and parallelism become increasingly important topics in software design. D was designed with this in mind. By default, all data is thread-local and threads communicate using a simple message passing system.

D has some other great stuff such as compile-time introspection and the ability to execute arbitrary code at compile-time, but we’ll gloss over those for now as they get a bit more involved.

Libraries

D has a nice standard library, including utilities for working with Unicode, regular expressions, JSON, HTTP (via curl), ZIP archives and DEFLATE-compressed streams (via zlib), hashing (including MD5 and the SHA family), pipes and process management, and more.

To make finding third-party libraries easier, D has a package manager known as DUB.

Moving at the speed of native

Languages like Python and Ruby sacrifice speed at the altar of convenience. These scripting languages are outperformed by native and more traditional JITted languages such as Java by an order of magnitude or two, but people use them because they are so damn good. Zach Holman captures this spirit in How GitHub Uses GitHub to Build GitHub when he exclaims,

I love Ruby. I’m not talking about the speed of Ruby, because it is slow as dicks. I’m talking about how fast it is to build something… It’s fun to try stuff out.

D breaks with the notion that the expressiveness of a language has to come at the cost of speed. Even with all the goodness shown above, D compiles to native code, offering a reference compiler for all major platforms as well as community-provided LLVM and GCC backends. Because the language was designed to be easy to parse, the long compilation time you might expect from a complex native language just isn’t there.

Give it a try!

Currently D seems to be competing for mindshare with Rust. Both aim to be a systems programming language loosely fitting the title “C++ replacement”. I think it’s a false premise that there can only be one “winner” here, but even if D loses out in the systems arena, it is still quite exciting for general application and web development. I highly encourage you to try it out next time you kick off a hobby project.