Why functional programmers should care about Rust
Sometimes, systems programming is necessary.

11 February 2018

It’s coming up to a year since I first started learning Scala, and moved to a team that believed in functional programming. For some, the syntax of Scala is initially uncomfortable. I didn’t have that problem. My theory is that I found Scala’s syntax easy to digest because I had already been doing things in Rust.

Let me explain, using a Java example as a point of contrast. If you were writing a Java function to calculate a factorial, it would look something like this:

// Java
public static long factorial(int n) {
    if (n <= 1) return 1;
    else return n * factorial(n-1);
}

There are two things that I’d like to point out here. Firstly, the types are to the left of the thing that they are annotating. It’s long factorial and int n. This is true of many other common languages that use a “C-like” syntax, including C++ and C#.

The second thing that I want to point out is that everything is a statement. You have to say return, or the program won’t know you meant for that number to be the result of your function.

Let’s look at that same function in Scala.

// Scala
def factorial(n: Int): Long = {
    if (n <= 1) 1
    else n * factorial(n-1)
}

Firstly, the types are on the right of the thing they are annotating. It’s n: Int. This isn’t just Scala being obtuse; there’s a long history of languages that put types to the right. It results in more consistent syntax when you let the compiler infer the types you meant.

Secondly, Scala is expression based. The if expression evaluates its condition and either returns the true case or the false case. You don’t need to say return, because the whole function body is an expression, which is evaluated and returned. This can make code more concise and readable if you use lambda functions regularly.

If you look at the same function written in Rust, you should understand why I think I was better prepared than somebody who had only used Java before.

// Rust
fn factorial(n: u32) -> u64 {
    if n <= 1 { 1 }
    else { n as u64 * factorial(n-1) }
}

The main difference between Rust and Scala if you only consider this single example example is that Rust is much more concerned about exactly how big the different types of integers are. Scala will implicitly cast the 32 bit n to 64 bits, whereas Rust wants you to explicitly ask for it.

Given this similarity, I would venture that is it easier for a Rust programmer to pick up Scala than it is for somebody that has only worked with languages that use C-like syntax.

To the point of this post, I think that it would be easier for a Scala programmer to pick up Rust than it would be for them to be productive in a different systems programming language, like C or C++.

Hold up, what’s a systems programming language?

Systems programming, as opposed to application programming, typically means writing programs to be consumed by other programs. For example, you might write an application in JavaScript, but a high performance JavaScript virtual machine would probably be written in a systems programming language.

Systems programming languages then are languages that have been designed with systems programming in mind. Typically, this means allowing a large amount of low level control, especially when it comes to performance characteristics and memory usage.

You can still write applications in systems programming languages of course, but as an application programmer you will typically be more productive if you reach for a different programming language. For example, Scala relies on garbage collection to manage its memory, which makes people writing Scala applications more productive but also limits its ability to be used for some systems programming situations.

When would I need a systems programming language?

Watch out for any situation that has strict real time performance requirements. Live signal processing (audio or video perhaps) and game development for examples.

As a real world example, there’s a Ruby on Rails profiler named Skylight. As a profiler, it needs to be running in the background without affecting the performance of the actual application it’s monitoring too much. By writing the profiler in Rust, a systems programming language, the Skylight team is able to keep the profiler as lean as possible.

If you have a piece of code that needs to run as fast as possible, and it makes sense for you to spend time making it faster instead of just buying a beefier server, then it may make sense to reach for a systems programming language.

The other realm that systems programming languages come up is when you can’t run things with many dependencies. There’s a project called Redox, which is an operating system kernel being written in Rust. You can’t run your code in the JVM if the code you’re writing is meant to load the system that allows the JVM to work.

I acknowledge that most application programmers won’t encounter these situations, but learning a systems programming language to be able to handle them when they do come up will make you a more versatile programmer.

If I need a systems programming language, why specifically Rust?

OK, so you’ve decided you need a systems programming language. Your normal day to day programming language is something high level, maybe something functional like Scala, but now you have a problem that you’re reaching outside of Scala to solve.

I think Rust is a good choice for a systems programming language in this case. Let me explain why.

Catch the subtle bugs at compile time

I was explaining to a colleague recently how strings work in C. When you’re in C, it’s normal to create an array of characters and pass around a pointer to the beginning of that array. You must do any bounds checking yourself when you’re putting characters into that array and you must put a null character at the end of the part you want to print. If you forget the null character, and then try to print the string, a bizarre thing happens. The terminal starts by printing your string, but without the null it doesn’t know when to stop. It keeps printing the contents of memory outside the bounds of your character array.

Here’s hoping that the memory after your string didn’t hold any valuable secrets.

Systems programming languages have bugs that can be subtle, difficult to debug, and fairly destructive. If you don’t specialise in a systems programming language, there is plenty of room to shoot yourself in the foot.

In the design of the Rust language, there’s an emphasis on safety while still giving the programmer control. It might take you longer to get something that the compiler can verify is safe, but it does catch subtle bugs long before they hit a production environment.

Control of your mutability

Data that isn’t allowed to change is easier to understand and less error prone. This is why Rust has taken the stance that making variables immutable is a better default. If you need to make a variable mutable, you need to specifically add mut to its declaration.

This is the opposite of languages like C, where the default is mutable data and you add const to make it immutable. Just changing the default influences what you’re likely to find in Rust code in the wild.

A monad by any other name…

Rust isn’t a functional language itself, but it has taken heavy inspiration from functional languages in its design and its core library.

As an example, let’s look at how Rust programs do error handling. Firstly, there are no exceptions. If your function has the possibility of failure, you need to encapsulate that in the return type.

To make this more convenient, Rust has a type called Result. Result is a union type, which means it can be in one of two possible states: either it’s an Ok (with the value you wanted to return from the function) or an Err (with some error details). This is remarkably similar to a type called Try in Scala!

let s = "one";

// When you parse a string to a number, there's a case where the
// string isn't a valid number, so parse returns a Result.
let parse_result = s.parse::<i32>();

// When we're adding 1, we only case about the successful parsing
// case, so we use map to use that.
let add_one_result = parse_result.map(|x| x+1);

// We can also pattern match on results if we want to handle the Ok
// case and the Error case.
match add_one_result {
    Ok(number) => println!("The number is {}", number),
    Err(parse_error) => eprintln!("The error was \"{}\"", parse_error)
};    

Unfortunately, the Rust standard library can be a bit inconsistent on function names if you compare it to other functional languages. You will find a map function on Results, but the flat_map function is called and.

Other types to take a look in Rust at are Option (you either have a value or be None), and Iterator (filter map and fold on various types of list).

Learn Rust now, so you’re ready when you need it

OK, that’s maybe a bit tongue in cheek, but if you have a problem that needs a systems programming language or are just interested in learning your first systems programming language, Rust is a good choice. It also has a fairly good free online book, The Rust Programming Language, which will teach you the core concepts.

If you come from a functional background, you’ll find it easier than you expect.

As always, remember to have fun!

Update: 2018-02-12

Literally the day after I published this post, Humble Bundle put up a new book bundle: Functional Programming books from O’Reilly. In the top tier is a book called Programming Rust. You can check out the bundle on the Humble Bundle store.

disclaimer: I’m part of the Humble partner program, so buying from them after following that link will help to support this blog.