How I have fun with Rust

Matheus Richard

Ruby is my main programming language, but in the last few months, I’ve been playing with Rust on side projects. Sometimes it was super fun, sometimes it was ultimately frustrating. To ensure I always break even, I came up with a few guidelines for myself.

Take the easy way out

A systems programming language introduces lots of new (and often hard) concepts. Some of them are similar but have trade-offs. When starting, I often would pick whatever was more performant – that’s the whole point of using Rust, right? –, but that led to much head-scratching. So, my advice is, instead, to take the easy way out, even if it’s not the most performant.

Applied to real situations, this results in:

  • Using String instead of &str in structs and enums.
  • Using Vec<T> instead of &[T] in structs and enums.
  • Using Box (so you can finally write that linked list you always wanted).

They’re just less cumbersome and more similar to what you might be used to in other languages.

Don’t write tests… if you don’t want to

It feels like blasphemy, given my Ruby background, but I’m writing (almost) no tests for my side projects. Rust’s type system is pretty good, so I need fewer tests to be confident my code works.

I only write one when I have a bug to reproduce (which is the perfect opportunity to practice Test-Driven Development).

The elephant in the room

Oh, the borrow checker… It’s absolutely cool, but also my main source of frustration.

Many times I tried using references and lifetimes, but I always ended up with hard to understand errors. Other times I’d try to fix the errors, only to end up with a different error. My advice is to avoid references and lifetimes as much as possible.

In practice, that means:

  • Using Box more often.
  • Using .clone() more often.
  • Not putting references in structs.

Don’t try to please the Rust sommeliers (especially your internal one)

Trying to write idiomatic Rust from day one is a bad idea. There’s no such thing as “perfect” code, so I advise you to be pragmatic. Here the old saying don’t let the perfect be the enemy of the good applies. Or, in programming terms, make it work, make it right, make it fast.

I write Rust like it’s an interpreted language and “go to lower level abstractions” when needed. Especially on side projects, be less diligent about your code and edge cases.

That means:

  • Using expect() to avoid dealing with Result/Option.
    • You can leave a message telling yourself to deal with it later.
    • I usually handle them, though. I don’t think these are hard like some other things.
    • If you’re lazy, use unwrap(). But really, write something.
  • Don’t use any fancy features like async, unsafe, and don’t write macros.
    • Boring code is good.
    • Unless it’s strictly necessary.
  • Don’t compare your proficiency in Rust with your main language. It’s not fair.

Why?

These guidelines are meant to make Rust more forgiving, and help me not to deal with too many things at once. I can focus more on the fun parts and don’t let my motivation die. That gets me to actually finish something.

Yes, the code uses more memory, but hopefully, you’ll have something working! I’ll take a slow-but-finished program over a fast-but-not-working-yet one any day. Also, it keeps you from prematurely optimizing. Let the profiler tell you what to focus on!

In a way, it’s like building a to-do list or a do-nothing script. You can make progress but still have a list of things to improve later. Once you get the fundamentals, you can start learning the fancy stuff at your own pace.