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 withResult
/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.