3

I read a couple of articles and it's still unclear to me. It looks like T and &T is kinda interchangeable as long as a compiler doesn't show any errors. But after I read an official doc I want to pass everything by reference to take advantage of borrowing.

Could you provide any simple rule about passing an arg as T against &T when T is an object/string? E.g., in C++ there're 3 options:

  • T – copy the value, can't mutate the current value
  • &T – don't create a copy, can mutate the current value
  • const &T – don't create a copy, can't mutate the current value

E.g., is it a good idea to pass by T if I want to deallocate T after it goes out of scope in my child function (the function I'm passing T to); and use &T if I want to use it my child function in a read-only mode and then continue to use it in my current (parent) function.

Thanks!

James Larkin
  • 541
  • 5
  • 18
  • See [In what scenarios are APIs that don't borrow preferred?](https://stackoverflow.com/a/53804449/493729) – Peter Hall Jan 24 '19 at 00:25
  • And [When should I use a reference instead of transferring ownership?](https://stackoverflow.com/a/50028001/493729) – Peter Hall Jan 24 '19 at 00:51

1 Answers1

6

These are the rules I personally use (in order).

  1. Pass by value (T) if the parameter has a generic type and the trait(s) that this generic type implements all take &self or &mut self but there is a blanket impl for &T or &mut T (respectively) for all types T that implement that trait (or these traits). For example, in std::io::Write, all methods take &mut self, but there is a blanket impl impl<'a, W: Write + ?Sized> Write for &'a mut W provided by the standard library. This means that although you accept a T (where T: Write) by value, one can pass a &mut T because &mut T also implements Write.

  2. Pass by value (T) if you must take ownership of the value (for example, because you pass it to another function/method that takes it by value, or because the alternatives would require potentially expensive clones).

  3. Pass by mutable reference (&mut T) if you must mutate the object (by calling other functions/methods that take the object by mutable reference, or just by overwriting it and you want the caller to see the new value) but do not need to take ownership of it.

  4. Pass by value (T) if the type is Copy and is small (my criterion for small is size_of::<T>() <= size_of::<usize>() * 2, but other people might have slightly different criteria). The primitive integer and floating-point types are examples of such types. Passing values of these types by reference would create an unnecessary indirection in memory, so the caller will have to perform an additional machine instruction to read it. When size_of::<T>() <= size_of::<usize>(), you're usually not saving anything by passing the value by reference because T and &T will usually be both passed in a single register (if the function has few enough parameters).

  5. Pass by shared reference (&T) otherwise.

In general, prefer passing by shared reference when possible. This avoids potentially expensive clones when the type is large or manages resources other than memory, and gives the most flexibility to the caller in how the value can be used after the call.

E.g., is it a good idea to pass by T if I want to deallocate T after it goes out of scope in my child function (the function I'm passing T to)

You'd better have a good reason for that! If you ever decide that you actually need to use the T later in the caller, then you'll have to change the callee's signature and update all call sites (because unlike in C++, where going from T to const T& is mostly transparent, going from T to &T in Rust is not: you must add a & in front of the argument in all call sites).

I recommend you use Clippy if you're not already using it. Clippy has a lint that can notify you if you write a function that takes an argument by value but the function doesn't need to take ownership of it (this lint used to warn by default, but it no longer does , so you have to enable it manually with #[warn(clippy::needless_pass_by_value)]).

Francis Gagné
  • 60,274
  • 7
  • 180
  • 155