T:Clone, but given a T:Clone + Copy

Consider this function:


pub fn dup<T: Clone>(v: Vec<T>) -> Vec<T> {
    let mut ans = vec![];
    for x in v.iter() {
        ans.push(x.clone())
    }
    ans
}


if we pass it a T: Clone + Copy, does Rust automatically know to optimize the x.clone() to use Copy at compile time?

If yes, how ?

If no, how do we build a separate function that gets invoked when T: Clone + Copy ?

1 Like

Rust doesn't implement Clone except through the derive mechanism, and what could it do except call Clone on everything recursively? If you do it yourself, there's obviously no guarantees regardless if it is Copy, because you can make Clone do whatever you want.

At the bottom of that recursion, Clone for Copy types is usually implemented as a copy. See here for instance. It's also #[inline], so the clone method shouldn't even be called.

2 Likes

You can implement Clone yourself, you don't have to derive it. If you're also Copy, you should have a an implementation of Clone that's the same as making a copy (but don't technically have to).

If the implementation does just do a copy, that's almost surely what it will be optimized into, a bitwise copy.

You cannot overload the behavior of Copy; it's always a bitwise copy. (You can implement it instead of deriving it, but it's just a marker trait, so there won't be much to the implementation.)

7 Likes

The purpose of the copy trait is to just be a marker that says clone is cheap, with some other details added on that affect how those types can be used. You don't have to worry at all about which implementation gets used when you need to duplicate it, it'll do the smart thing.

Technically speaking, there's no guarantee that its Clone impl is cheap if it also impl Copy. Though such impl should be considered as a logic bug.

#[derive(Debug, Copy)]
struct Foo(i32);

impl Clone for Foo {
    fn clone(&self) -> Self {
        println!("doing some expensive computation...");
        Foo(self.0 + 1)
    }
}

fn main() {
    let orig = Foo(5);
    let copied = orig;
    let cloned = orig.clone();
    println!("copied: {:?}, cloned: {:?}", copied, cloned)
}

Result:

doing some expensive computation...
copied: Foo(5), cloned: Foo(6)
2 Likes

Note that per RFC 1521-copy-clone-semantics - The Rust RFC Book, the compiler is allowed to replace a call to .clone() with a bitwise copy if the type also implements Copy.

So I agree it's a logic bug (like having inconsistent Eq and Hash) but in some ways a particularly bad one.

12 Likes

That is a very interesting situation. It feels like the compiler should somehow forbid a programmer from doing that, but they didn't so I guess there must have been a good reason. What situation could possibly exist where a type that is copy+clone would need to two different ways of duplicating itself?

It's because there's no mechanism in the language that you could use to prevent you from weird Clone implementations. Disallowing it would be require a new language feature or some special-cased code in the compiler that specifically disallows it.

The classic problematic situation here is the following:

struct Wrapper<T>(T);

impl<T : Clone> Clone for Wrapper<T> {
    fn clone (self: &'_ Wrapper<T>)
      -> Wrapper<T>
    {
        Wrapper(self.0.clone())
    }
}

impl<T : Copy> Copy for Wrapper<T> {}

This is an example where, for Wrapper<T> : Copy, the Clone impl of Wrapper<T> is not the *self "default" bit-copy: it is actually overridden with the Wrapper(self.0.clone()) instead. Assuming the inner self.0.clone() behaves, that overridden impl ought to be a bit-copy and so everything is a bit copy and all is good. But it's still "hard" for the language to allow these necessary derive impls while also forbidding the buggy ones.

4 Likes

And in the general case, mathematically impossible for the compiler to catch every possible case. The compiler could special case the most common cases, but whether it should is a different discussion.

1 Like

There is a clippy lint against it.

Option<T> is a real-world example of this. There was an attempt to use specialization to make the optimized Clone work as a Copy, but that stalled out.

1 Like

Isn't this implementation the same that is generated with #[derive(Clone)]? If it is, why compiler cannot forbid manual impl of Clone if there also exists some impl (derived or manual) of Copy?

Well, for starters, derives are supposed to be Just Sugar™ for something that one should always be able to manually write.

  • That being said, there is (an annoying) precedent for derive having a special capability: PartialEq, Eq. Indeed, these are required to be derived in order for constants of that type to be usable as "literal patterns": such patterns will perform comparisons matching "structural equality" semantics, which would otherwise be susceptible not to match the "semantic equality" that a manual impl of PartialEq would cause.

Given that precedent, I guess forbidding manual impls for that case could be acceptable, if it weren't for the current implementation of the derives being a bit dumb.

Consider, for instance:

#[derive(Clone)]
struct MyPtr<T>(*const T);

impl<T> Copy for MyPtr<T> {} // Error, missing `T : Clone` bound !??

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.