Skip to content

Instantly share code, notes, and snippets.

@flaki
Last active August 29, 2015 14:20
Show Gist options
  • Save flaki/35e4286a1f77a94c2c7b to your computer and use it in GitHub Desktop.
Save flaki/35e4286a1f77a94c2c7b to your computer and use it in GitHub Desktop.
Rust 1.0.0 release introductory article for Hacks v5

Diving into Rust for the first time

A computer program may crash or hang due to a wide variety of reasons. Memory access violations/segmentation faults are most of the time rooted in unsafe memory handling, while hangs and unpredictable behavior could sometimes be attributed to concurrent, unsynced or out-of-sync access to memory.

Rust is a new programming language, the primary goal of which is to eliminate these kinds of issues by creating a language in which memory safety and safe concurrency are just as important as keeping the generated code as fast as possible.
Rust has just recently reached its first stable release, 1.0.0 — but it already has a vibrant community, blooming ecosystem of crates (modules) in its package manager and developers harnessing its capabilities in a variety of projects.

Why should I care?

If you were into systems programming before, this the above intro should have been enough to pique your interest in trying Rust — but what if you have never touched the low-level yet? What if the last time you heard the words "C" and "stack/heap allocation" was 10 years ago in CompSci 101?

The real boon is that, while Rust provides the performance generally only seen in low-level systems languages — most of the time it will feel like a high-level language! Here are just a few examples of how you could leverage Rust in practical applications:

“I want to hack on hardware/write Internet-of-Things applications”

The IoT era and the expansion of the maker movement brought about a real democratization of hardware projects. Whether it is the Raspberry Pi, Arduino or one of the young titans like the BeagleBone or Tessel, you could choose from a slew of languages to code your hardware projects in, even Python or JavaScript.

There are times, however, when the performance these languages offer is simply not adequate. Other times the microcontroller hardware you are aiming for is just not suited to the runtimes these languages require: slow chips with tiny memory reserves, or ultra-low power applications still require a close-to-the-metal language. Traditionally that language has usually been C — but as you might have guessed, Rust is the new kid on the block.

Rust already supports a wide variety of exotic platforms. While some of this is still in experimental phase, support includes generic ARM hardware, the Texas Instruments TIVA dev board and even the Raspberry Pi.
Some of the newest IoT boards like the Tessel 2 even come with official, out of the box Rust support!

“I'm operating high-performance computing applications that need to scale to zillions of cores!”

Studies prove Rust is already great for HPC. You don't even have to rewrite your whole application in Rust: Rust's flexible Foreign Function Interface provides efficient C bindings so you could start rewriting your app module by module, slowly transitioning towards a better developer experience. This new experience will result in a performance on par with the old code or better, but with a more maintanable codebase with less errors, which scales much better on a high number of cores.

“I simply need something fast!

Rust is great for rewriting performance-sensitive parts of your application in. It interfaces well with other languages via FFI and has a tiny runtime that lets it compete with C and C++ in most cases, even when resources are limited.

Despite the work-in-progress nature of the language, there are already business-critical, live production applications that are making a good use of Rust for some time now: for example Yehuda Katz's startup, Skylight uses high-performance Rust code embedded in a ruby gem for data-crunching.

Getting started with Rust

If any of the above got you sufficiently hyped, one question might arise:

"So what is the best way to dive into Rust, then?"

There are lots of great all-rounder tutorials covering Rust in general, and more specific aspects aspects of the language. For example, the Rust blog has great articles on various facets of developing Rust applications. There are also some excellent talks that will help get you started - one of those is Aaron Turon's talk at Stanford University. He explains the main concepts and motivations behind Rust nicely, and the talk in general serves as the perfect aperitif, starting you on your journey to learning the language:

Embed? Aaron Turon's Stanford Seminar on Rust

Here it is worth noting that you should be wary of old (that is, more than a few months old, from 2014 or earlier) talks and posts on Rust (and especially code!). Since the language was less stable then, you might stumble into things that do not even exist in 1.0 (much less compatible with current, post-1.0.0 codebases).

That said, no talks or tutorials could replace actually writing code, could they? Rust's got you covered in that regard, too! The Rust Book has been conceived to help you get started — from installation, through your very first Hello World to providing an in-depth reference for all the core language features, you will find it all there in one convenient place.

There is one more resource worth mentioning here: Rust by Example guides you through Rust's key features,from the most basic ones to the arcane powers of traits, macros and the FFI. What really makes Rust By Example an invaluable resource is the live-editable, in-browser runnable examples present in all of the articles. It even saves you the hassle of downloading & compiling Rust, as most of the examples you could try (and edit) from the comfort of your living room couch, just as they are!

Not that downloading and installing Rust was much of a trouble, anyway. You could head to the homepage and download the appropriate binary/installer from right on the first page. The downloads contain all the tools you need to get started (like rustc, the compiler, or cargo the package manager), and are available pre-built for all platforms (Windows, Linux & OSX).

So what is it then, that makes Rust so unique, so different?

The "Rust Way"

Arguably, it's Rust's unique ownership model that allows it to really shine — eliminating whole classes of errors related to threading & memory management, while easing development & discovering errors. It is also invaluable in keeping the language runtime minimal and the compiler output lean and performant.

The ownership-parable

The basic principle of Rust's ownership model is that every resource could belong to one and only one "owner". A "resource" here could be anything — from a piece of memory, to an open network socket or either something more abstract like a mutex lock. The owner of said resource is then responsible for using, possibly lending out the resource (to other users), and finally cleaning it up.
All this happens automatically, with the help of scopes and bindings: bindings either own or borrow values, and last for the end of their scope. As bindings go out of scope, they either give their borrowed resource back, or in case they were the owner, free the resource.

What's even better is that the checks required for the ownership model to work are executed and enforced by the Rust compiler and "borrow checker" during compilation, which in turn results in various benefits.

For one, since the correct operation of a program is asserted at compilation time — code that compiles could generally be considered safe with regard to most common types of memory & concurrency errors (such as use-after-free bugs or data races). This is possible because Rust would complain at compilation time if the programmer attempted to do something that violated the above principles, which in turn helps keep the language runtime minimal. By deferring those checks to compilation time, there is no need to perform them during runtime (code is already "safe"), or in the case of memory allocations, there is no need for runtime garbage collection.

While the thorough explanation of the ownership model is outside the scope of this article, both the Rust Book (linked above) and various talks and articles excel in explaining its principles. Understanding ownership principles and the borrow checker is essential in that these two serve both as the baseline for understanding the language, and the source for other other powerful aspects of the language.

For example, one of those aspects in which Rust really shines is concurrency and parallelism.

Two birds — with one language construct

Shared mutable state is the root of all evil. Most languages attempt to deal with this problem through the ‘mutable’ part, but Rust deals with it by solving the ‘shared’ part.
The Rust Book

Besides memory safety, paralellism and concurrency is the second most important focus in Rust's philosophy. It might be surprising, but the ownership system (coupled with a few handy traits) provides powerful tools for threading, synchronization and concurrent data access.

When you try some of the built-in concurrency primitives and start digging deeper into the language, what may come as a surprise, is that those primitives are provided by the standard library, rather than being part of the language core itself. This means that coming up with new ways to do concurrent programming is in the hands of library authors — rather than being hard-wired into Rust, restricted by the language creators' future plans.

Performance or expressiveness? Choose both!

The other benefit of Rust's model is what's succintly expressed as "zero-cost abstractions". Thanks to the static type system, the carefully selected features and having no garbage collector Rust programs compile into performant and predictable code that's on par with code written in traditional, low-level languages (such as C/C++).
By moving the various checks out of the runtime and getting rid of garbage collection the resulting code mimics the performance characteristics of those low-level languages, while the language itself can still remain much more expressive. The (seemingly) high level constructs (strings, collections, higher order functions, etc.) mostly avoid the runtime performance hit commonly associated with them, and since the Rust compiler's output is in the LLVM intermediate representation, the final machine code makes good use of the LLVM compiler's cross-platform benefits and all additional (current and future) optimizations automatically.

We could go on for hours about how with Rust you never have to worry about closing sockets or freeing memory, or how traits & macros give you the incredible flexibility for overloading operators, or even working with Rust's functional aspect, juggling iterators, closures and higher order functions… but we wouldn't want to overwhelm you early on. Sooner or later you will come across those anyway — and it's much more fun to discover for yourself exactly how deep the rabbit hole is.
So now instead, let's finally see some code!

Say hello to Rust

Without further ado — et voilá, your very first Rust code:

fn main() {
    println!("Hello, Rust!");
}

/PS: could we made these an interactive "play-pen", similar to those at rustbyexample.com?/

What, that's it? Okay, I agree this might have been a bit anti-climactic, but there really are only so many ways one can write a Hello World program in Rust. Please bear with us while we elaborate with another, slightly more complex example — and for that, we will be relying on another timeless classic…

Let's see what's all this buzz is about!

We are going old-school, with this other classic oldtimer, the FizzBuzz program! Fizzbuzz is a classic algorithm in which we count updwards for all eternity, replacing some of the numbers with fizz (for numbers evenly divisible by 3), buzz (for numbers divisible by 5) or fizzbuzz (divisible by 3 and 5).

Also to show our point (and to help you familiarize yourself with Rust's semantics), we have included the C and Python versions of the same algorithm:

Imperative fizzbuzz - C version

#include <stdio.h>

int main(void)
{
    int num;
    for(num=1; num<101; ++num)
    {
        if( (num%3)==0 && (num%5)==0 ) {
            printf("fizzbuzz\n");
        } else if((num%3)==0) {
            printf("fizz\n");
        } else if((num%5)==0) {
            printf("buzz\n");
        } else {
            printf("%d\n",num);
        }
    }

    return 0;
}

Imperative fizzbuzz - Python version

for num in xrange(1,101):
    if num % 5 == 0 and num % 3 == 0:
        print "fizzbuzz"
    elif num % 3 == 0:
        print "fizz"
    elif num % 5 == 0:
        print "buzz"
    else:
        print num

Imperative fizzbuzz - Rust version

fn main() {
    for num in 1..101 {
        if num % 3 == 0 && num % 5 == 0 {
            println!("fizzbuzz");
        } else if num % 3 == 0 {
            println!("fizz");
        } else if num % 5 == 0 {
            println!("buzz");
        } else {
            println!("{}", num);
        }
    }
}

As you can see, Rust's syntax is pretty straightforward - it is somewhere in between C's and Python's complexity. You might have noticed the range notation in the for loop's declaration (similar to its Python counterpart), which should hint to more advanced features. To expand on this and show you the true flexibility Rust can offer, in the second example we will crank our fizzbuzz up a notch!

/PS: also it would be nice if we could group the 3 + 3 (imperative + functional) examples together to a switchable block, like those at http://tessel.io /

Let's try this again, shall we?

Rust calls itself multi-paradigm — so let's put this to the test, and rewrite our fizzbuzz-example in… functional style! To help put the code in perspective, we will implementations of the same algorithm in two other language — this time in Ruby and JavaScript (the EcmaScript 6 flavor):

Functional fizzbuzz - Ruby version

1.upto(100)
    .map{ |num|
        case
        when num % 15 == 0 then "fizzbuzz"
        when num % 3  == 0 then "fizz"
        when num % 5  == 0 then "buzz"
        else num
        end
    }.map{ |output|
        puts output
    }

Functional fizzbuzz - JavaScript (ES6) version

(new Array(100)).fill(0).map((i,n) => n+1)
    .map((num) => {
        if (num%3 === 0 && num%5 === 0) {
            return "fizzbuzz";
        } else if (num%3 === 0) {
            return "fizz";
        } else if (num%5 === 0) {
            return "buzz";
        } else {
            return num;
        }
    })
    .map(output => console.log(output));

Functional fizzbuzz - version

fn main() {
    (1..101)
        .map(|num| {
            match (num%3, num%5) { // Wow! Pattern Matching FTW!
                (0, 0) => "fizzbuzz".to_string(),
                (0, _) => "fizz".to_string(),
                (_, 0) => "buzz".to_string(),
                     _ => format!("{}", num) 
            }
        })
        .map(|output| { println!("{}", output); output })
        .collect::<Vec<_>>();
}

Now we are talking! It's not only that we could mimic Ruby's functional style with cloures and functional method calls, but thanks to Rust's powerful pattern matching we can even trump Ruby's expressiveness.

Note: Rust's iterators are lazy, so we actually need the .collect() call (which is called a consumer) for this to work — leaving that out would cause that none of the above code would get executed. Check out the consumers section of the Rust Book to learn more about this.

Below you can see what happens in the pattern matching phase of our fizzbuzz-example a bit more elaborated:

...
    // For pattern matching, we build a tuple, containing
    // the remainders for integer division of num by 3 and 5
    match (num%3, num%5) {
        // When "num" is divisible by 3 AND 5 both
        // (both remainders are 0)
        // -> return the string "fizzbuzz"
        (0, 0) => "fizzbuzz".to_string(),

        // When "num" is divisible by 3 (the remainder is 0)
        // Is "num" divisible by 5? -> we don't care
        // -> return the string "fizz"
        (0, _) => "fizz".to_string(),

        // When "num" is divisible by 5 (the remainder is 0)
        // Is "num" divisible by 3? -> we don't care
        // -> return the string "buzz"
        (_, 0) => "buzz".to_string(),

        // In any other cases, just return "num"
        // Note, that matching must be exhaustive (that is, cover
        // all possible outcomes) - this is enforced by the compiler!
             _ => format!("{}", num)
    }
...

Here we can't go into great depths about how pattern matching or destructuring works, or what tuples are — there are great articles on all these topics in the Rust Book, the Rust Blog or over at Rust By Example, but we think this is a great way to demonstrate the features and nuances that make Rust both powerful and effective at the same time.

Now that you are all set and eager to dive in to creating cool stuff with Rust, you might want to ask what should that cool stuff be?

What should be my first Rust project?

Well, that depends! You could start small, write a small app, a tiny library — upload it to crates.io. People have created huge amounts of exciting projects in Rust already — from pet *nix-like kernels, to embedded hardware projects, from games to web server frameworks, proving that the only limit is your imagination.

If you would like to start small, but contribute useful stuff while learning the ropes and intricacies of Rust programming, consider contributing to some of the projects already thriving on GitHub (or even the Rust compiler itself — which is also written in Rust).
You could also start by rewriting small parts of your production app, and experimenting with the results. Rust's Foreign Function Interface (or FFI for short) is intended as a drop-in replacement for C code — this means, with minimal effort you could replace small parts of your application with Rust implementations of the same code. But interoperability doesn't stop there — these C bindings work both ways. This in part means you could use all standard C libraries in your Rust apps — or (since a lot of programming languages come with standard C interface libraries) make calls back-and-forth between Rust and Python, Ruby or even Go!
There are virtually no languages you couldn't work together with: want to compile Rust to JavaScript, maybe even use inline assembly in your code? You could!

Now get out there and build something awesome with Rust!
Also please don't forget - Rust is not simply a programming language — but it's also a community. Should you ever feel stuck, don't be shy to ask for help, either on the Forums or IRC, or just share your thoughts & creations on Reddit.

Because Rust is indeed great — but it is the people like You who are going to make it awesome!

/PS: Skeleton & collected sources from my research: https://medium.com/@slsoftworks/1eee43ea526a /

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment