Learning Rust
I’m always looking for new challenges. With my background mostly being in web development, I have little to no experience in low-level languages. In the past I’ve tinkered with C, and go-lang. This summer, I decided to try and learn Rust. Rust aims to be a very safe and performant systems-level language. It aims to provide powerful zero-cost abstractions and interesting features like Generics, pattern matching and guaranteed memory safety. The memory safety and thread safety promised by rust are intriguing as both C and go lack these guarantees. Rust recently hit a stable release, which was another reason I was interested.
A Real Exercise
Instead of building hello world applications, or simply following tutorials, I decided to pick a real challenge. My initial goal was to build a CLI client for Slack. After realizing how enormous this goal was, I decided to scale my ambition back a bit. Instead I wanted to build a statsd client, and possibly a server. In the last few months I’ve made good progress. The statsd-client and statsd-server are both on github. The client is pretty complete, and the server works reasonably well given the amount of time I’ve invested.
Syntax
I found the syntax for rust pretty easy to pick up with a few exceptions. The language makes pervasive use of short forms. For example fn
instead of function. I found it a bit hard to remember all the truncated forms and ended up re-reading the documentation many times. Thankfully rust has only a few ‘magic symbols’. The most common so far are &
(reference) and *
(de-reference).
The language does feature some pretty great syntax decisions though:
- let x = 5;
- match x {
- 1 => println!("one"),
- 2 => println!("two"),
- 3 => println!("three"),
- 4 => println!("four"),
- 5 => println!("five"),
- _ => println!("something else"),
- }
The match
keyword is like a supercharged switch statement. Combined with rust’s expressions you can do neat things like:
- let y = match x {
- 1 | 2 => x
- _ => x * 2
- };
The documentation on patterns gives a more complete reference on all the powerful things you can do with match
and patterns in rust..
Strings
Having only really used go-lang in the past, I found the type system in rust pretty similar. One big difference was how strings are handled. In rust there are two string types – String
and str
. These types are somewhat interchangeable but not really. When to use each one, and converting between them confused me for quite some time. The primary advantages of String
that I know of are:
String
is heap allocated, can be enlarged or mutated.str
is a fixed size and stack allocated when possible.String
can easily be returned from functions. Returning astr
requires significantly more work.
With the above in mind, you still generally want to use str
whenever possible, as they consume fewer resources.
Type System and Safety
One of the big wins from rust’s type system is the promise that code that compiles should never encounter memory corruption. The way rust does this is through its ownership model and the ‘borrow checker’. In rust each variable binding can only have one ‘owner’. This owner can be transferred, but there can only ever be one owner. If other functions need a read-only copy of a variable then can temporarily ‘borrow’ a variable using a reference. The rust documentation gives this example for ownership:
- // This function takes ownership as there is no `&` (reference) being used.
- fn take(v: Vec<i32>) {
- // what happens here isn’t important.
- }
- let v = vec![1, 2, 3];
- take(v);
- println!("v[0] is: {}", v[0]);
The above code will not compile in rust. Instead you’d have to mark parameters as reference types and have the function ‘borrow’ the value:
- fn borrow(v: &Vec<i32>) {
- // what happens here isn’t important.
- }
- let v = vec![1, 2, 3];
- borrow(&v);
- println!("v[0] is: {}", v[0]);
The above code compiles and runs just fine. Because, the caller lends v
to the borrow
function by using &
instead of transferring ownership. This ownership model is probably the single most frustrating thing I’ve found while learning rust. The ownership model takes a bit to grok, however the compiler errors are quite helpful in resolving mistakes.
Tooling
Rust provides a fantastic build tool out of the box. Cargo is the kind of tool I really missed in go. It handles compilation, testing, benchmarking, predictable builds, and dependency management. While the go community has many solutions to achieve predictable builds, and dependency management, none are part of the official tools. Having these basic tools built into rust makes it much easier for beginners like myself to get started.
Conclusion
Rust is fantastic, and I think it has a very bright future. While the library support is a bit lacking at times, there are lots of great opportunities to develop solutions and share them with the community. Speaking of which, every interaction I’ve had with the rust community has been really positive. From the bug trackers, to the IRC channel, people have been really helpful in handling my newbie questions.
Such features like pattern matching or strict type system support come from functional languages such as ML or Ocaml.
The syntax there (let, fn (fun in ml), match, !, _) all the same. So I would not say it is totally new language, as it is based on idea and math conceptions coming in early 1990th
I was build my diploma project in university 15 years ago using Ocaml and speed of development and code quality was really incredible if compare with mainstream languages like C++ or Delhi. Most amazing for me was fact that ocaml code was equal or sometimes better performance then C code solving same task.
Finally, I really happy that this tendention now come to large developers world and not limited in small group of mathematics.
Evgeny Tomenko on 9/1/15