More Fun With Rust

Filed under rust on January 28, 2022

I’ve been playing with Rust some more lately, and I’m starting to really appreciate a few things about the language.

I’ve been going through Hands on Rust, and building the little Roguelike game the book lays out. I’ve learned how to use Cargo, how modules work and how to stop worrying about macros. It’s been a very enlightening experience, and I’ve enjoyed it immensely. I have also been working on an implementation of the DCPU-16 computer using Rust, though I’m not quite done and still need to implement interrupts.

Expressions

Something I’ve really loved in Rust is that nearly any block of code can return a value. For example, instead of a ternary operator, I can just use a plain if statement

let a = if b > 10 { "abc"} else { "cba" };

Similarly, I can use a match statement to do something similar

let a = match b {
  Ok(z) => z.foo,
  Err(z) => "bar",
};

It allows for some concise, but still readable blocks of code without memorise too much syntax.

The Compiler

Rust’s compiler is incredible. Still being a rust newbie, the error messages are incredibly detailed and useful, even offering good suggestions on how to fix things. For example, from habit I tried to return a &Machine instead of Machine type from a function. The rust compiler told me the following

error[E0106]: missing lifetime specifier
  --> src/machine.rs:39:21
   |
39 |     pub fn new() -> &Machine {
   |                     ^ expected named lifetime parameter
   |
   = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
   |
39 |     pub fn new() -> &'static Machine {
   |                     ~~~~~~~~

Which was helpful in that it gave a fair bit of insight into lifetimes. If you forget a semicolon, it can usually figure out what you meant. One of the things that always frustrated me about C and C++ was the lack of compiler support before you shot yourself in the foot, and Rust’s compiler has been built in a way to stop you way before that happens.

Explicit mutability

I kind of questioned the point a little bit when I first started learning Rust, but I realised as I started implementing my own code that it became much easier to track what functions mutated state and which did not.

Semi related to something at work, I implemented a library to do maths with some generated protobuf structs. I wanted to implement the functions as pass by value so that the original structs would not be touched if someone went through later and made modifications, but ended up having to do pass by reference because the generated objects had sync.mutex which the linter didn’t like. Had I had Rust’s mut keyword, I wouldn’t have had an issue.

Cargo

The cargo package manager is also brilliant. Being able to choose features to compile into your application and easily choose the architecture you want to target in the cargo.toml file makes for a very nice developer experience.

Something that could be a little better

One thing that has tripped me up a couple of times has been trying to use a u16 type as an array index and encountering compiler errors because you can’t index an array with that type. Which is annoying, the compiler suggests the answer is to cast to usize, but it can’t just do it automatically. I get why, but it just leads to some gross code.

All in all, my experience has been incredibly positive. I’ve very much enjoyed learning Rust, and it’s made me rethink how I code in go a little bit as well, particularly when thinking about unintended side effects of functions. Hoping I can use it some more in the future.


Stephen Gream

Written by Stephen Gream who lives and works in Melbourne, Australia. You should follow him on Minds