I must admit, the first few times I saw some Rust code I thought it was too complex, that the learning curve, versus the rewards it claimed, was too high. I thought it would never pick up and become a popular programming language.

// A simple Ruzzle solver in Rust.
// Finds all the word combinations in a 4x4 grid of letters. Some parts omitted.
// Source: https://gist.github.com/macournoyer/c891d3a9233c18778252c8a367ee68c6

struct Board {
    dictionary: HashSet<String>,
    letters: String,
}

impl Board {
    pub fn letter_at(&self, i: usize) -> String {
        self.letters.chars().nth(i).unwrap().to_string()
    }

    pub fn find_words(&self) -> HashSet<String> {
        let mut found = HashSet::new();
        for i in 0..16 {
            found.extend(self.find_from_letter(i, self.letter_at(i), vec![i]));
        }
        found
    }

    pub fn find_from_letter(&self, i: usize, word: String, mut visited: Vec<usize>) -> HashSet<String> {
        let mut found = HashSet::new();

        // Find all the possible letter combinations from the current position
        for m in &MOVES[i] {
            if *m == 0 { continue; }
            let new_i = (i as isize + m) as usize;
            if visited.contains(&new_i) { continue; }
            visited.push(new_i);
            let word = format!("{}{}", word, self.letter_at(new_i));
            // Only words in the dictionary are valid
            if word.len() >= MIN_WORD_SIZE && self.dictionary.contains(&word) {
                found.insert(word.clone());
            }
            if word.len() <= MAX_WORD_SIZE {
                found.extend(self.find_from_letter(new_i, word.clone(), visited.clone()));
            }
        }

        found
    }
}

fn main() {
    println!("Loading dictionary ...");
    let dictionary = load_dict("words.txt");
    println!("{} words loaded", dictionary.len());

    let mut letters = String::new();
    println!("Enter 16 letters: ");
    io::stdin().read_to_string(&mut letters).unwrap();
    letters.pop(); // Remove \n

    let board = Board { dictionary, letters };

    println!("Found words: {:?}", board.find_words());
}

For the past three years, I’ve been using Rust almost daily. It started pretty rough. It takes a while to get over the dense syntax. But eventually, I realized that it is so, because a lot needs to be expressed in as few lines as possible.

After using it for a while I realized, Rust is rough, Rust has no mercy, Rust tells you like it is: you’ve been doing some stuff all wrong for quite some time now. And, this hurts quite a bit at first. You’ll feel like: “hey! let me do it my way”. But no. Rust won’t let you, and rightfully so.

Ownership

The first thing I realized is that Rust is very annoying when trying to share stuff.

Imagine you want to create a logger and just pass it around to objects you create so they can use it. You know the logger will exist for the entire duration of the program.

let logger = Logger::new();

let muffin = Muffin::new(logger);
let coffee = Coffee::new(logger); // <= NO! Can't do that!

But Rust says NO! You have “moved” logger to the Muffin::new method, it is gone inside that method. So Rust forces you to think: do you want to share it for a brief moment? Then borrow it: &logger. Share it for a bit longer? Rc::new(logger). Share it across threads? Arc::new(logger). Allow modifications? RefCell / Mutex / RwLock, etc. You see the picture. Lots of options. Lots of tiny decisions early on. It can make your head dizzy.

It would be easy in this case to say: “I don’t care! I know logger will exist for the whole duration of the program. I know it will no be modified.” The thing is, you know it now, but us humans, we forget. And that’s how you end up at 2 AM on a weekend debugging a piece-of-shit-code, yours.

Rust is your friend. It tells you the hard truth: “Hey dumbass, don’t do this! You’ll regret it, at 2AM, on a weekend”. It hurts your feelings at first, but you learn to trust and accept it.

This unpleasant part of learning Rust is often what people call “fighting the borrow checker”.

No NULL pointers

I call it my billion-dollar mistake. It was the invention of the null reference […] Tony Hoare

There is no NULL, null, nil in Rust. At least, not unless you play with the unsafe parts.

// If a variable can be empty, you must wrap it in an Option (Some or None).
let mut maybe_muffin = Some(muffin);

// No more muffin for you
maybe_muffin = None;

This even led me to start using Optional in Java. But to get the full benefits, you need to have the whole thing (runtime, libraries) not use null, which Rust has.

By consequence, it’s impossible1 to create a null pointer in your Rust code. One problem less to worry about!

Thread-safe guaranteed!

Some of the most difficult to track bugs are those around threads. It’s difficult share stuff efficiently when dealing with thread. And quite often, we “forget” to make our code thread-safe.

But Rust, do not allow to write non-thread-safe code by default. “What?” you say. “That must be very verbose, cumbersome, stupid, that I need to make all my code thread-safe”.

No, you only need to make the code you run in threads thread-safe. But even better, the Rust compiler will check for you if you code is already thread-safe and not bother you if there are no issues.

This is achieved in Rust with the Sync and Send traits2.

Once again, Rust makes it impossible1 to create non-thread-safe code.

How awesome is that?

Congrats to the Rust team!

It is difficult to make a brand new language. But what is incredibly more difficult, is building stable, complete and pleasant runtime libraries. The amount of work the Rust team has and is putting into making everything better on a daily basis makes it a very exciting environment to use.

  1. “impossible” as in “difficult”. If you really want to do something that Rust doesn’t consider safe, you simply wrap your code in an unsafe block.  2

  2. Traits are kind-of-like interfaces in Java or Go. They contain declarations, but no implementations, although they can provide a default one. Some traits contain no declarations, and are used a markers, eg.: this value can be sent across threads.