Posted on 3 mins read

Record scratch. Freeze frame. You’re writing a function. What are you thinking about?

If you’re using a mainstream, high-level, memory-managed language like C#, Python, Java, or JavaScript, you’re thinking about:

  • The parameters
  • Their types
  • What the function will do with the information they contain
  • The return value
  • Its type

Which are good and reasonable things to think about, and most of the time, for most things, they’re all you should think about. That’s why JavaScript and Python are the most popular programming languages: they only make you think about the things a normal, well-adjusted person wants to think about when they code.

Rust is different.

You have to think about more things when you write Rust.

(Aside: So why write Rust at all? Because it breaks the dichotomy of choice between fast, memory-unsafe languages (C and C++) and slow, memory-safe languages (the other ones I mentioned). Rust is both fast and memory-safe. Haters thought it was impossible. You may wonder: if you’re getting speed and memory safety, what are you sacrificing? Speed of development, that’s what. Rust takes more time to write, but it runs like a housecat at bath time.)

Specifically, for each parameter to a Rust function, you have to ask yourself, what do I want to do with this value?

> I just want to look at it:

You probably want an immutable reference: &param. This is what you do for parameters to public functions most of the time, and if you’re new to Rust you might feel weird doing it, because why are there so many ampersands? What do they want from me? What do they mean? Take a deep breath. This is normal. &Vec<&Vec<&MyStruct>> is normal. You might think it would be better to make immutable references the default and get rid of all the extra & boilerplate. You’d be wrong, apparently, and I will not elaborate.

> I want to modify it:

You probably want a mutable reference: &mut param.

> I want to decide when it dies:

Wow. Okay. In that case, just take param.

Actual situations when you might do this (credit to the good folks on Mastodon):

  • You’re inserting a value into a long-lived data structure, and the value should exist exactly as long as the structure does.
  • You definitely need an owned value (like a String, so you can append to it). The caller can make a copy if they need to, but if they have an instance they don’t mind handing over, it’ll save them the trouble.
  • You’re consuming a resource, like a file or buffer, and you want to use Rust’s compiler to guarantee no one else tries to use it after you’re done. (Think RAII.)
  • You’re writing a private method and always taking ownership of the value makes sense in context of its extremely limited and well-understood usage.
  • You need to destroy the value—cast it into the fire!—and you know why.

The Rust book refers to the first two options (look at it, modify it) as “borrowing” and this last option (decide when it dies) as “taking ownership” or “moving.” I propose we call it “stealing” instead. That makes the consequences a little clearer. When you define a parameter, your choices are “borrow” or “steal.” Borrowing is super duper normal; the & syntax doesn’t mean you’re doing anything weird or special. Stealing is a little less normal, but hey, gotta do what you gotta do.