My First Month With Rust
8 minutes read · 2017-03-29Roughly a month has passed since I started using the Rust programming language as my “daily driver”, and I feel like it would be interesting to share my early impressions and thoughts about the language.
NB: The title of this post is not entirely accurate, since I had been toying on and off with the language in my free time for a while. Rust has a steep learning curve, so don't feel demotivated if you don't hit the ground running in the first month.
Rust?
Rust is a systems programming language with a focus on execution speed, memory and data-race safety. It's sponsored primarily by Mozilla, who are using it to develop their next generation browser engine, Servo.
Learning Curve
My first impression of Rust was that it was significantly harder to learn than most languages I had experimented with, perhaps with the exception of Haskell. During the first weeks, it felt like I didn't know how to properly structure anything beyond the simplest programs. That was quite frustrating*.
My advice if you're struggling in this stage is to power through and keep trying to use the language, even if you can't fully understand what's going on yet or get everything to work. Much like learning git
and vim
, it does pay off later on. After some time, it's finally going to “click”, and you'll start to develop an intuition of how things work.
The following were my major pain points during my first month with rust:
Pain Point #1: Move Semantics
Most other languages provide either copy semantics or reference semantics for dealing with values. What that means is: if you pass a variable x
into a function f()
, it will receive either a copy of the value in x
or a reference to it. (Depending on the language and the type of x
)
Rust does away with that, and opts to use move semantics† by default for most custom data types‡. Attempting to compile the following code:
fn f(x: String) {
// Do something with x
}
fn main() {
let x = String::from("Hello");
f(x);
println!("{}", x);
}
Run
Will result in an error, since the value of x
is consumed by f()
, and cannot be reused by println!()
:
rustc 1.16.0 (30cf806ef 2017-03-10) error[E0382]: use of moved value: `x` --> <anon>:8:20 | 7 | f(x); | - value moved here 8 | println!("{}", x); | ^ value used here after move | = note: move occurs because `x` has type `std::string::String`, which does not implement the `Copy` trait error: aborting due to previous error
Instead, you need to either explicitly clone the value of x
, or take a reference and pass that instead:
fn f(x: String) {
// Do something with x
}
fn g(x: &String) {
// Do something with x
}
fn main() {
let x = String::from("Hello");
f(x.clone());
g(&x);
println!("{}", x);
}
Run
This forces you to think about ownership, which is very convenient for providing automatic memory management and compile-time optimizations. However, it does take some time getting used to.
* It doesn't help that most of the “classic” CS 101 assignments that people will commonly implement when learning a new language—e.g. linked lists, binary trees, hash tables—all traditionally involve shared mutable state, which is heavily discouraged by the language in the first place.↩
† Rust was not the first programming language to introduce move semantics for values. C++11 implements it to some extent via move constructors. ↩
‡ Primitive data types—and in fact all types that implement the Copy
trait—are exempt from this rule, and are instead passed by copy. ↩