Let’s say you’re new to Rust. Maybe you’ve read TRPL and worked through Rustlings or YARR. You can write enough Rust to solve problems, and can fix compiler errors when they crop up. Now you come to a more nebulous question: how do you write good Rust? And how do you write Rust that won’t require mortal combat with the borrow checker?
Start by letting go of a no-compromises approach to performance.
Don’t Be Afraid to Clone
Because Rust makes cloning explicit, I get the feeling that a lot of newcomers find it stressful. How expensive of an operation is a .clone()
? Unfortunately, the answer can range from “very expensive” to “very cheap.” Reference-counted structures like an Arc
or a channel can be cloned very cheaply. Generally strings like usernames, filenames, or paths are small, and therefore cheap. In these cases clone first and think later.
In cases where you’re uncertain, I would still strongly recommend cloning! You can always profile your code to see if the clones are performance drags, but I promise you it’s unlikely. In most cases choosing more suitable data structures, adding caches, or improving memory locality will all pay bigger dividends than agonizingly avoiding allocation.
Don’t Store References
The number one way new-to-Rust programmers make their lives harder is to deal with lifetime annotations. I have been writing Rust for a little under ten years; I have maintained a game library and written a hobby compiler. I still find code that makes heavy use of lifetime annotations confusing. How do you avoid them? Don’t store references in data structures. Instead, clone the values (if they can be cloned cheaply) or use an Arc
or Cow
as appropriate.
Use Send
and Sync
containers
Technically, Rc
and RefCell
are lighter-weight than Arc
and Mutex
. If you’re 100% sure that a given bit of code will never become concurrent, by all means use them. For example: audio code tends to run in a dedicated thread, so you know that there’s no reason to share that data. In any other case I strongly recommend going with the Send
and Sync
alternatives instead. You’ll be unlikely to notice the performance cost, and you’ll be grateful to not have to refactor swathes of your program if you want to introduce some kind of parallelism later.
(Bonus) Check out the Rust API Guidelines
They explain some key heuristics to writing good Rust. Here are the few entries that I think are both most important and least intuitive: