A new era of statically typed, natively compiled, programming languages has arrived sweeping aside the old choice between performance and productivity while also eliminating runtime dependencies on an interpreter or VM. Two of the top contenders are Rust and Go, how does a programmer choose between them?

For some applications the choice is simple. Go’s lightweight concurrency and excellent networking libraries make implementing high-performance network clients and servers a joy. Rust lacks GC and a runtime, making it more suitable for shared libraries that may be linked into programs written in other languages.

Rust is a much more complicated language than Go, with foreign concepts such as ownership, borrowing, and lifetimes that make the learning curve quite steep. However I’ll argue that it’s well worth learning and that Rust is an excellent language for writing clear, concise, high-level code.

## Code

A common programming task is iterating through a stream of values and grouping them in a hash table according to some logic. Here are two programs that iterate the range of unsigned integers between 1 and 128, keeping track of the minimum, maximum, and last 4 values of the even and odd numbers.

RustGo
``````
use std::cmp::{min, max};
use std::collections::HashMap;
use std::u32;

struct Stats {
min:   u32,
max:   u32,
last4: [u32; 4],
}

fn main() {
let mut map = HashMap::new();

for i in 1..129 {
let key = i % 2 == 0;
let stats = map.entry(key).or_insert_with(|| {
Stats {
min:   u32::MAX,
max:   u32::MIN,
last4: [0; 4],
}
});

stats.min = min(i, stats.min);
stats.max = max(i, stats.max);
shift(i, &mut stats.last4[..]);
}
}
``````
``````
package main

import "fmt"
import "math"

type Stats struct {
min   uint32
max   uint32
last4 [4]uint32
}

func main() {
smap := map[bool]Stats{}

for i := uint32(1); i < 129; i++ {
key := i%2 == 0
stats, ok := smap[key]
if !ok {
stats = Stats{
min: math.MaxUint32,
}
}

if i < stats.min {
stats.min = i
}

if i > stats.max {
stats.max = i
}

copy(stats.last4[1:], stats.last4[0:])
stats.last4[0] = i

smap[key] = stats
}
}
``````

The structure of both programs is quite similar. The Rust one appears significantly shorter but I’m cheating and left out the definition of the `shift()` function which isn’t built-in. I’ll come back to that in a bit.

``````
fn shift<T: Copy>(n: T, slice: &mut [T]) {
if slice.len() > 0 {
unsafe {
let ptr = slice.as_mut_ptr();
std::ptr::copy(ptr, ptr.offset(1), slice.len()-1);
*ptr = n;
}
}
}
``````

## Hash map entries

The most interesting part of this simple problem is how the program handles updating an existing entry or adding a new entry to the map. This is a very common pattern and some languages provide elegant APIs (Rust, Ruby, Python) for creating a new value when the key is not present.

RustGo
``````
let stats = map.entry(key).or_insert_with(|| {
Stats {
min:   u32::MAX,
max:   u32::MIN,
last4: [0; 4],
}
});

``````
``````
stats, ok := smap[key]
if !ok {
stats = Stats{
min: math.MaxUint32,
}
}

...

smap[key] = stats
``````

Indexing a Go map always returns a value, initialized to zero if the key doesn’t exist, and a boolean indicating whether the key exists. This is elegant when the zero value is useful without modification and means only the `min` field needs to be set here. Rust requires explicit initialization or use of the Default trait.

The Go programmer must remember to copy the new or updated struct back into the the map and failure to do so is a common source of bugs. A different approach is storing `*Stats` pointers which eliminates the final map assignment and copy but results in structs scattered throughout the heap rather than in a contiguous array which is much more efficient.

The Rust code uses the Entry API to return a reference to either the existing, or newly inserted, map entry which can be mutated directly. This eliminates the need to choose between copies or storing pointers.

Go does not allow taking a reference to a map entry because the reference may become invalid when the map is modified. Rust’s ownership system ensures that the map cannot be modified while holding a reference to an entry.

## Min & max

RustGo
``````
stats.min = min(i, stats.min);
stats.max = max(i, stats.max);

``````
``````
if i < stats.min {
stats.min = i
}

if i > stats.max {
stats.max = i
}

``````

Go lacks generics so the `math.Min` and `math.Max` functions are only defined for `float64`. The programmer must manually implement variants for every type or use the more verbose `if` statements. Rust supports min, max, and other comparisons on any type that implements Ord.

## Copy & shift

Rust has no safe equivalent of Go’s `copy()` function so the code must resort to an unsafe block to shift the first three elements of the slice over and assign the newest element to index zero. Alternatively the code could have used a temporary array to perform the shift safely at the cost of two copies.

RustGo
``````
shift(i, &mut stats.last4[..]);

...

fn shift<T: Copy>(n: T, slice: &mut [T]) {
if slice.len() > 0 {
unsafe {
let ptr = slice.as_mut_ptr();
std::ptr::copy(ptr, ptr.offset(1), slice.len()-1);
*ptr = n;
}
}
}
``````
``````
copy(stats.last4[1:], stats.last4[0:])
stats.last4[0] = i

``````

The `shift()` function is simple and with generics need only be written once but calls an unsafe function that could cause memory corruption if the pointer offset or count calculations are incorrect. An earlier version did not contain the `if slice.len() > 0` check which led to panics in debug mode and memory corruption in release mode due to unsigned integer wraparound when passed a zero-length slice.

## Conclusion

The two programs shown here illustrate a common programming task but are very simple and do not really play to the strengths of either language. In my opinion Rust comes out ahead due its rich collections API, ownership allowing safe mutable references, and generics. Rust is not a small or simple language but the complexity serves a purpose in delivering a language with the performance characteristics of low-level languages and the expressiveness of high-level languages.

In my opinion Rust code will generally be more clear and concise than the equivalent solution in Go. The `min`, `max`, and `shift` functions, not to mention Rust’s `try!`/`?` vs. Go’s `if err != nil` pattern, demonstrate how generics and macros allow a single implementation of common patterns rather than copy & paste.

Regarding the choice between Rust and Go, I will likely choose Rust by default and Go when the solution would benefit from Go’s concurrency model or richer collection of libraries including those for building network clients and servers. In the later case I may even write the core of the application in Rust and call into it from Go.