Detecting the last clone

Hi! Let's start with a minimal example:

fn consumer(v: MyClonableType) { ... }

fn foo() {
    let val = MyClonableType::new();
    
     consumer(val.clone());
     consumer(val.clone());
     consumer(val.clone());
     consumer(val.clone());
     consumer(val); // val is not returned, we may just use it
}

After a round of refactoring, I removed the last two consumer() invocations, and didn't notice that I may also remove the last clone. In the real-world there were a lot of code around, I simply overlooked that.

My question: is it possible to detect such cases, when we may return the original value, rather its clone? I run clippy, but it didn't suggest anything.

It would be a great clippy lint, but I don't see one.

This may be the redundant_clone lint, currently in nursery status.

5 Likes

If your code can be expressed as a loop, you can use std::iter::repeat_n():

use std::iter;

fn foo() {
    let val = MyClonableType::new();
    
    for val in iter::repeat_n(val, 5) {
        consumer(val);
    }
}

repeat_n() knows that on its last element, it doesn’t need to clone. It's technically possible to also use it in a non-looping fashion:

fn foo() {
    let val = MyClonableType::new();
    
    let vals = iter::repeat_n(val, 5);
    consumer(vals.next().unwrap());
    consumer(vals.next().unwrap());
    consumer(vals.next().unwrap());
    consumer(vals.next().unwrap());
    consumer(vals.next().unwrap());
}

but you probably don’t want to actually do that.

3 Likes