Three Tips for Tackling Rust

Rust is a relatively new systems programming language that has been garnering a lot of attention over the past year or two as a compelling alternative to languages like C and C++. My colleague Job Vranish gave a brief introduction to Rust last summer, and John Van Enk has been singing Rust’s praises within Atomic. My curiosity piqued, I decided to take a look for myself.

I’ve been dabbling in Rust for about a month now, and there’s a lot to love about it. Personally, I’m most interested in the high-level ideas like pattern matching and destructuring, higher-order functions and closures, and generic types and functions. Programmers who work “closer to the metal” than I do, will likely appreciate Rust’s memory safety and low run-time overhead (many of Rust’s features and safety guarantees happen at compile time).

My experience with Rust has been overwhelmingly positive, but I’ve encountered a few roadblocks while learning it. If you’re considering learning Rust too, here are some tips to ensure your journey is a smooth one.

1. Be aware of changes to the language.

Rust is still in rapid development, and the language sometimes changes in ways that break old code. These changes are made for good reasons, and they are documented in the release notes when they do happen, but even so they can be a bit inconvenient as a language user. Unless you’re willing to occasionally go back and update your code, you’ll probably want to wait until the language has stabilized.

On the other hand, if you like living on the cutting edge, I recommend installing the nightly, or compiling the latest code directly from the source repository. You should also subscribe to the rust-dev mailing list to keep up to date with the latest changes to the language.

2. Read all the fine manuals.

The Rust documentation is rather good. You’ll want to start with the official tutorial, then follow up with the official guides. Other tutorials and guides can be useful, but keep in mind that they can quickly fall out of date because of the rapid development of the language.

After you’ve been through the introductory materials, you’re ready for the reference manual and the standard library reference. Don’t bother reading these all the way through. Instead, just look up specific information as you need it.

The standard library reference is a great help for learning what functions are available. But because of the way much of Rust’s functionality is divided into traits, you’ll have to do a bit of digging to get a complete picture of all the functions applicable to a particular data type. Remember to read not only the docs for the type itself (like std::str), but also the docs for relevant traits (like std::str::Str and std::str::StrSlice), related types (like std::str::Chars), and perhaps the module that defines the type. Also keep in mind that some functions (or traits) require data to be owned and/or mutable, while others can be used with read-only references.

3. Watch out for syntax pitfalls.

Rust is fairly syntax- and punctuation-heavy, at least to my eyes after years of Ruby and Lisp/Scheme programming. It can be off-putting and intimidating at first, but for the most part I’ve been able to figure it out with the help of the documentation and tutorials. But, there have been a few things that were not well-covered, and had me stumped for a long time.

To illustrate these points, consider these two hypothetical source files:

// main.rs
mod foo;

fn main() {
    let s = ~"Hello, Rust!";
    let f = foo::Foo::<~str>::new(&s);
}
// foo.rs
pub struct Foo<'a, T> {
    pub bar: &'a T,
    pub quz: ~str
}

impl<'a, T> Foo<'a, T> {
    pub fn new(b: &'a T) -> Foo<'a, T> {
        Foo {
            bar: b,
            quz: ::std::str::with_capacity(64)
        }
    }
}
  • When you import another file as a module (mod foo; in the code above), the contents of the other file are implicitly wrapped in a module with the same name as the file. That is why my struct Foo is written as foo::Foo in main.rs, even though I never explicitly defined a module foo.

  • Also because of the implicit module foo, I must write ::std::str::with_capacity within foo.rs. If I omitted the initial namespace qualifier (::), which tells Rust to start looking from the top-level namespace, Rust would look for a namespace std within the implicit module foo. If foo.rs were being compiled as a top-level file, it would work with or without the initial namespace qualifier. But it’s probably clearest and safest to always write the initial namespace qualifier.

  • When you call a standalone method of a generic type, the specialization must be separated from the type name with ::. E.g. you must write foo::Foo::<~str>::new(&s), not foo::Foo<~str>::new(&s) or foo::Foo::new<~str>(&s), which were my first two guesses.

  • When you write an impl that involves a lifetime, you need to specify the lifetime after the keyword impl (e.g. impl<'a>), as well as anywhere the lifetime is referenced (Foo<'a, T>, &'a T, etc.).

Rust is definitely not the easiest language to learn, but keeping these tips in mind will help make your learning experience just a bit smoother.