Skip to content

rust.clone_in_loop

Performance Medium

Detects expensive .clone() calls inside loops.

Cloning in loops multiplies allocation costs:

  • Memory pressure — N iterations = N allocations
  • CPU overhead — Deep structures take time to clone
  • Hidden cost — Clone looks innocent but can be expensive
  • Scales with input — Performance degrades with larger data
// ❌ Before
fn process_all(config: Config, items: &[Item]) {
for item in items {
process_item(item, config.clone()); // Clones on every iteration
}
}

If items has 10,000 elements and Config is large, that’s 10,000 expensive clones.

// ✅ After
fn process_all(config: &Config, items: &[Item]) {
for item in items {
process_item(item, config); // Just borrows
}
}
// Or if ownership is needed, use Arc
fn process_all(config: Arc<Config>, items: &[Item]) {
for item in items {
process_item(item, Arc::clone(&config)); // Cheap ref count bump
}
}
  • .clone() calls on non-Copy types inside loops
  • Clone inside closures passed to iterators
  • Repeated cloning of the same variable

Unfault suggests borrowing or Arc wrapping based on usage patterns.

// Cheap clones (optimized or Copy)
Arc::clone(&arc) // Just ref count
Rc::clone(&rc) // Just ref count
i32::clone(&num) // Copy type
// Expensive clones (avoid in loops)
String::clone(&s) // Full heap allocation
Vec::clone(&v) // Full heap allocation + element clones
HashMap::clone(&m) // Full allocation + all key/value clones
// Use references
fn process(data: &Data) { }
// Use Arc for shared ownership
let shared = Arc::new(expensive_data);
for item in items {
let data = Arc::clone(&shared);
spawn(async move { use_data(item, data) });
}
// Clone once before loop
let template = expensive_template.clone();
for item in items {
let mut copy = template.clone(); // Still clones, but template is prepared
}
// Use Cow for conditional cloning
use std::borrow::Cow;
fn process(data: Cow<'_, Data>) {
// Only clones if mutation needed
}