Goodbye Rust, and Hello, D!


After quite a bit of thought and consideration, I have decided to abandon my study of Rust, and move on to D.

I had been learning Rust for a while now, and I have become quite comfortable with it, but there are a few reasons that prompted me to move on to another systems language that would suit me better. Now while D is by no means perfect, I found the following advantages already:

  • A very consistent and intuitive (to me) core language. I have been learning it for only a couple of days now, and I’ve had quite a few “wow, it works exactly as I expected” moments already.
  • Very welcoming and helpful community that actually focuses on the technical side of things rather than getting sidetracked by social causes
  • A wide variety of compilers (DMD, GDC, LDC) targeted for both gcc and LLVM.
  • A stupendously fast compilation cycle. For small programs, I have found
    the compilation times to be on par with Go’s
  • A much saner syntax (in my very early estimation) that actually is quite readable (Rust’s syntax is not quite so readable for anything less-than-trivial in my opinion), and finally
  • Arrays (arguably the most important data structure) are actually sane, consistent, and very much logically intuitive in D unlike the mess that’s C (and C++).
  • Extremely powerful metaprogramming capabilities (an area I am particularly interested in)
  • A much much safer language than C++ while being much more programmer-friendly than Rust. Rust in that sense offloads the whole mental load of memory management onto the developer (and not in a crazy fun way like C or C++).

My use-cases for a systems programming language are quite simple, really – I don’t intend to do OS-level development (at least not in the foreseeable future), and so the GC dependency of D does not affect me in that sense. My research as well as my interactions with the wonderful folks over at forum.dlang.org have convinced me though that even with the GC, performance is still topnotch for a wide variety of systems-level programming.

As part of my learning process (just two days in to be exact), I thought of implementing two programs just to test out the waters before I dived in, and both of them were implemented with much less hassle (infinitely so to be honest), and they just worked beautifully the first time!

Insertion Sort

This is a barebones implementation of Insertion Sort in D:

// insertion_sort.d
import std.stdio;

void insertionSort(int[] arr) {
    foreach(i; 1..arr.length) {
        int key = arr[i];
        int j = cast(int)i-1;
        
        while (j >= 0 && arr[j] > key) {
            arr[j+1] = arr[j];
            j--;
        }
        arr[j+1] = key;
    }
}

void main() {
    int[] array = [100,-199,0,20,2,3,4,5,0,0,1,200];
    
    writefln("Before sorting: %s", array);
    
    insertionSort(array);
    
    writefln("After sorting: %s", array);
}

And here’s a test run:

sh-4.3$ dmd -run insertion_sort.d                                                                                                                                                   
Before sorting: [100, -199, 0, 20, 2, 3, 4, 5, 0, 0, 1, 200]                                                                                                              
After sorting: [-199, 0, 0, 0, 1, 2, 3, 4, 5, 20, 100, 200]  

Simple, and elegant! In fact, this code is directly readable to and understandable by anyone who has even the least bit of familiarity with C, C++, Java, or any member of the extended ALGOL family!

A line number closure

For my second program, I wanted to try out the line number closure (courtesy Hoyte) in D. As it turns out, either D is very logically designed, or my mind is quite attuned to D (or perhaps both!). It was quite pleasurable to be honest. Of course, at this stage, I have no idea if the code is idiomatic D or not. Anyway, let’s have some fun!

// line_closure.d
import std.stdio;

void delegate() getLineClosure() {
    int lineNum = 0;
    
    void printLineNumber() {
        lineNum++;
        
        writefln("Current line: %s", lineNum);
    }
    
    return &printLineNumber;
}

void main() {
    void delegate() x = getLineClosure();
    
    foreach(i; 0..5) {
        x();
    }
    writeln();
    
    void delegate() y = getLineClosure();
    
    foreach(j; 0..3) {
        y();
    }
}

and let’s take it for a spin:

sh-4.3$ dmd -run line_closure.d                                                                                                                                           
Current line: 1                                                                                                                                                           
Current line: 2                                                                                                                                                           
Current line: 3                                                                                                                                                           
Current line: 4                                                                                                                                                           
Current line: 5                                                                                                                                                           
                                                                                                                                                                          
Current line: 1                                                                                                                                                           
Current line: 2                                                                                                                                                           
Current line: 3    

Beautiful! The code probably deserves a bit of explanation – in D, functions are (as far as I can tell), first-class objects, and we can nest functions inside almost any scope. However, a function that is nested within a function or block scope is given a special name – a delegate. So the return type of the getLineClosure function:

void delegate()

indicates that we are returning a delegate which takes no parameters and returns nothing. Also note that we explicitly return the address of the delegate unlike in C, where the name of the function itself is the function pointer.

For this example to work, the delegate has to be able to capture the local variable, lineNum of course, and so delegates in D essentially function as closures in other Functional Programming languages. Also note the clean syntax of foreach for looping (C style loops are also supported, of course).

In conclusion, one major difference that I observed between Rust and D (I’m much more familiar with Rust, of course) is that I can more fully focus on the problem itself with D whereas with Rust, I have to always be aware of (and worrying about) what I am doing with the bindings/variables in my program, and I don’t think it’s altogether a question of getting familiar and comfortable with that. The ownership and borrowing concepts of Rust are, in and of themselves, trivial. However, the entire onus of managing proper memory behaviour is entirely on the developer. I wonder how much that would actually scale in real life. I suppose we’ll know the answer when people start developing large industry-standard (as much as I despise that term) in Rust.

Advertisements
Goodbye Rust, and Hello, D!

10 thoughts on “Goodbye Rust, and Hello, D!

    1. Timmy Jose says:

      Indeed! I am still a toddler in the D world, but I am amazed at how smooth the flow is when solving (admittedly at my current level, small) problems!

      Like

  1. XavierAP says:

    Why that cast? Variable ‘i’ is an array’s index, you’re going to use ‘j’ in the same way, should be the same type, which you don’t even need to know:
    auto j = i – 1; // use auto, Luke

    Like

    1. Anonymouse says:

      Yes, trap there. Array indexes are unsigned size_t (uint/ulong), so treating them as ints requires casts, which will just be cast back when you use them to index again. But the compiler will hide that step from you.

      It doesn’t have to be idiomatic to work just fine, which is relaxing.

      Liked by 2 people

    2. Timmy Jose says:

      You’re absolutely right! In this case though, there is a problem.

      The problem is more with this specific insertion sort algorithm, which is the same sort of problem that I ran into with Rust as well (for this particular algorithm). The issue is this – this algorithm could potentially make the var j go down to -1, which throws (as in the case of this sample input), a Range violation. (core.exception.RangeError@insertion_sort.d(9): Range violation). That is why I had to do the nasty casting. So the fault is more with this C-like condition checking for the inner while loop. I should probably have used a saner implementation!

      Like

      1. XavierAP says:

        Aha sorry I hadn’t read all of your code and the algorithm itself. But these lines should be equivalent (and have fewer +/- operations but meh)?
        auto j = i
        while (j > 0 && arr[j] > key) {
        arr[j] = arr[j-1];
        j–;
        }
        arr[j] = key;

        Like

Speak your mind!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s