In Rust, it is often better to avoid cloning if possible to avoid unnecessary allocation.
Here is a list of techniques I use most often to do that.
All titles are links to Rust playgrounds if you want to play with the examples.
Implement the Copy trait
For small data types, cloning might actually be the cheapest option.
There is a special trait for that named Copy.
Whenever possible, you should implement that trait on your data types. It makes the code a lot simpler because you don't have to worry about ownership at all. The borrow checker will let you pass your types implementing Copy around freely, for example:
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
fn draw_point(Point {x, y}: Point) {
println!("Drawing {x}, {y}");
}
let point = Point { x: 1, y: -4 };
// We can pass point by value indefinitely.
// To make the compiler unhappy, try removing Copy from the derive annotation above.
draw_point(point);
draw_point(point);
draw_point(point);
See the doc to know when you can, should and can't implement Copy for your type.
In practice, the most common usage I have for it is for newtypes and enums:
#[derive(Copy, Clone)]
struct NewType(i32);
#[derive(Copy, Clone)]
enum SomeEnum {
Variant1,
Variant2(i32),
}
Take parameters by reference
Oftentimes, a function doesn't need to take ownership of its parameters and is able to work with a reference, for example:
fn print_by_val(value: String) {
println!("Printing {value}");
}
fn print_by_ref(value: &String) {
println!("Printing {value}");
}
let money = String::from("money");
// To print infinite money with print_by_val, we must expensively clone repeatedly
print_by_val(money.clone());
print_by_val(money.clone());
print_by_val(money.clone());
// But we get free money with print_by_ref!
print_by_ref(&money);
print_by_ref(&money);
print_by_ref(&money);
Note that print_by_ref should ideally take a &str instead of a &String, see this clippy lint. I use &String here to make the difference between print_by_val and print_by_ref clearer.
Use the proper iterator
Depending on whether you only need a reference or ownership of the values of an iterable, you should use .iter() or .into_iter() (or .iter_mut() for a mutable reference).
struct NewType(String);
let data = vec![String::from("foo"), String::from("bar"), String::from("baz")];
// If you don't need `data` afterward, avoid
let new_types = data.iter().map(|elem| NewType(elem.clone())).collect::<Vec<_>>();
// Prefer
let new_types = data.into_iter().map(NewType).collect::<Vec<_>>();
Similarly, by default a for loop is similar to .into_iter(), so call .iter() first if you don't want to drop your data and only need a reference:
let data = vec![String::from("foo"), String::from("bar"), String::from("baz")];
// If you need `data` afterward, avoid
for elem in data.clone() {
println!("{elem}");
}
// Prefer
for elem in data.iter() {
println!("{elem}");
}
Have closures capturing by value also return the value
Sometimes you are forced to have a closure capture by value, for example because the closure is sent to another thread. However, the closure doesn't really need to consume the value and you need to use it after the closure call. In that situation, the trick is to have the captured variable be returned by the closure:
let world = String::from("world");
// Avoid
let cloned_world = world.clone();
let computed = std::thread::spawn(move || format!("Hello {cloned_world}!"))
.join()
.unwrap();
println!("{computed}");
// Prefer
let (world, computed) = std::thread::spawn(|| {
let computed = format!("Hello {world}!");
(world, computed)
})
.join()
.unwrap();
println!("{computed}");
// We still have our world here
println!("Hello {world}!");
Note that if you are not clear about the use of the move keyword here, you might want to read my article on closures.
Conclusion
This is not an exhaustive list but those are the techniques I use the most often. If you would like to discuss them or share your own, you can do so on Reddit or Hacker News.