rollingBalls
rollingBalls

Reputation: 1878

Refactoring out `clone` when Copy trait is not implemented?

Is there a way to get rid of clone(), given the restrictions I've noted in the comments? I would really like to know if it's possible to use borrowing in this case, where modifying the third-party function signature is not possible.

// We should keep the "data" hidden from the consumer
mod le_library {
    pub struct Foobar {
        data: Vec<i32>  // Something that doesn't implement Copy
    }

    impl Foobar {
        pub fn new() -> Foobar {
            Foobar {
                data: vec![1, 2, 3],
            }
        }

        pub fn foo(&self) -> String {
            let i = third_party(self.data.clone()); // Refactor out clone?

            format!("{}{}", "foo!", i)
        }
    }

    // Can't change the signature, suppose this comes from a crate
    pub fn third_party(data:Vec<i32>) -> i32 {
        data[0]
    }
}

use le_library::Foobar;

fn main() {
    let foobar = Foobar::new();
    let foo = foobar.foo(); 
    let foo2 = foobar.foo(); 
    println!("{}", foo);
    println!("{}", foo2);
}

playground

Upvotes: 2

Views: 370

Answers (2)

Vladimir Matveev
Vladimir Matveev

Reputation: 127851

As long as your foo() method accepts &self, it is not possible, because the

pub fn third_party(data: Vec<i32>) -> i32

signature is quite unambiguous: regardless of what this third_party function does, it's API states that it needs its own instance of Vec, by value. This precludes using borrowing of any form, and because foo() accepts self by reference, you can't really do anything except for cloning.

Also, supposedly this third_party is written without any weird unsafe hacks, so it is quite safe to assume that the Vec which is passed into it is eventually dropped and deallocated. Therefore, unsafely creating a copy of the original Vec without cloning it (by copying internal pointers) is out of question - you'll definitely get a use-after-free if you do it.

While your question does not state it, the fact that you want to preserve the original value of data is kind of a natural assumption. If this assumption can be relaxed, and you're actually okay with giving the data instance out and e.g. replacing it with an empty vector internally, then there are several things you can potentially do:

  1. Switch foo(&self) to foo(&mut self), then you can quite easily extract data and replace it with an empty vector.
  2. Use Cell or RefCell to store the data. This way, you can continue to use foo(&self), at the cost of some runtime checks when you extract the value out of a cell and replace it with some default value.

Both these approaches, however, will result in you losing the original Vec. With the given third-party API there is no way around that.

If you still can somehow influence this external API, then the best solution would be to change it to accept &[i32], which can easily be obtained from Vec<i32> with borrowing.

Upvotes: 2

Charles Gleason
Charles Gleason

Reputation: 416

No, you can't get rid of the call to clone here.

The problem here is with the third-party library. As the function third_party is written now, it's true that it could be using an &Vec<i32>; it doesn't require ownership, since it's just moving out a value that's Copy. However, since the implementation is outside of your control, there's nothing preventing the person maintaining the function from changing it to take advantage of owning the Vec. It's possible that whatever it is doing would be easier or require less memory if it were allowed to overwrite the provided memory, and the function writer is leaving the door open to do so in the future. If that's not the case, it might be worth suggesting a change to the third-party function's signature and relying on clone in the meantime.

Upvotes: 2

Related Questions