The semantic of `Clone` and `Copy`

The types that implement Copy merely bit-wise copy and do not transfer their ownerships to the destination variables. The weird thing in the design of Copy is it requires implementations of its first implement Clone, however, as aforementioned, the types implement Copy does not implicitly make x.clone() call, merely a simple memcpy, the implementation of Clone for a T: Copy must adhere what we say in the document, that is, the behavior of clone must simply return *self, which makes clone may have an error-prone implementation, and in a certain degree, Copy and Clone do not have an established relationship because we can totally implement a complicated "copy" for Clone but Copy just bit-wise copy, which is inconsistent with Copy: Clone.

Why do we not make Copy as a single simple marker trait without requiring Clone as its super trait? We can totally do something like

pub trait Copy{}
impl<T:Copy> Clone for T{
    fn clone(&self)->Self{
        *self
   }
}

The default implementation of Clone for T: Copy prevent user-defined incorrect implementation of Clone for T: Copy and the semantics will be consistent.

2 Likes

Types like Option<T> implement Clone where T: Clone, but only implement Copy where T: Copy. So the blanket impl isn’t good enough.

Your proposed "solution" is a breaking change for starters.

Is this good enough?

impl<T: Copy> !Clone for T{
    fn clone(&self)->Self{
      unreachable!()
   }
}

What I meant was, the Copy trait just indicates the bit-wise copy, which does not require a Clone(specifically, a user-defined clone). In other words, any type T that implements Copy performs a bit-wise copy in any context, which is equivalent to the move operation except for not transferring ownership.

This would mean that any type implementing Copy won't be able to also implement Clone. This means that you can't have a single function with bound T: Clone which is usable with both types that implement Copy and types that implement Clone.

Moreover this still remains a big breaking change. I don't think you can change anything at this point, even in newer editions.

ps: the syntax is wrong. Negative impls always have an empty body

5 Likes

I just want to make Copy decouple from Clone and the compiler can give the type that implements Copy a default implementation of Clone, which prevents user-defined error-prone implementation of Clone. This does make sense for Rust(i.e. prevent errors in safe code).

Moreover, Copy only means bit-wise copy in any context regardless of how the user implements Clone, or the user even does not need to implement Clone for the type that is Copy. By this logic, Copy and Clone do not have an established/dependency relationship, so we can detach Copy from Clone.

The simple way is we do not require that the type implement Copy must implement Clone. The Copy trait does not need Clone as its super trait.

I'm sorry but that is very unlikely to happen. As said before, doing that is a breaking change, which makes it an absolute no-go.

So how does one implement Clone for Option<T> when T: Clone and Copy when T: Copy? With your proposal this would be illegal because the implementations would overlap. You need to consider that there's a ton of code out there that implements both Clone and Copy. Any proposal regarding decoupling Clone and Copy needs to address how to guarantee that such code will continue to compile with the same semantics.

Also be careful with "default" implementations and negative reasoning. You can very easily end up with something equivalent to specialization, which is currently unsound. It's nice to prevent surprising behaviour in safe code, but this shouldn't come at the price of introducing fragile features which may break safety invariants, which is much worse than surprising behaviour.

Copy and Clone not having a relationship in that specific logic doesn't mean they don't have a relationship at all. Clone represents the ability to create a copy of something by potentially executing code, while Copy represents the same ability to create a copy of something by only doing bitwise copies. Thus Copy effectively represents a subset of Clone. Having Clone guaranteed to be implemented for any type that implements Copy is thus a desired property because it allows to abstract over both.

7 Likes

Just to add: it has long been considered desirable that #[derive(Copy)] could also implement Clone without having to write #[derive(Copy, Clone)]. (This can be made a nonbreaking change by changing the behavior of the prelude Copy builtin derive macro over an edition.)

6 Likes

Copy is "harder" to implement, because it is "less likely" to be able to be done; not all types can do a memcpy, because of RAII. On the flip side, you're saying that Clone isn't necessary for all types because Copy works just fine. However, that shouldn't mean that they don't implement the Clone trait, instead, they should just make the Clone trait have the same behavior as the Copy trait. Doing a Clone doesn't need to be something that implies that a lot of work is needed to be done. It's a lot more convenient that Clone can be used as something that most objects can do. The entire purpose is that one is in a narrower scope of traits than the other, so the programmer can choose how far they want to reach, not that they have overlapping scopes, which would make using Copy and Clone extremely confusing. So respectfully, I very much disagree.

3 Likes

Notice that trait bounds are not only their because you need them directly, but also for logical reasons. Having a type that is Copy but not Clone is problematic as it cannot be passed to functions restricted to Copy types. As for the blanket impl. This will quickly result in duplicate implementations, e.g. we have:

impl<T: Copy, N: usize> Copy for [T; N] {}

impl<T: Clone, N: usize> Clone for [T; N] {
    [...]
}

This would render a duplicate implementation for Clone for e.g. [i32; 8].

The following code does not print anything on stable Rust:

struct Foo<'a>(&'a str);

impl<'a> Clone for Foo<'a> {
    fn clone(&self) -> Self {
        println!("Running `Clone` impl");
        Self(self.0)
    }
}

impl Copy for Foo<'static> {}

fn main() {
    let s = "hi".to_string();
    // `a` is not a `Box<Foo<'static>>`, and therefore doesn't implement `Copy`
    let a = Box::new(Foo(&*s));
    // And yet this line uses the `Copy` impl that theoretically doesn't apply!
    // Nothing is printed.
    let _ = a.clone();
}

Given this fact, I do think it would make sense to find a way for impl Copy for T to automatically create a specialized Clone impl, since that is effectively what happens anyway. Doing so would also help resolve an issue with const traits, where Copy doesn't imply const Clone.

That 'static/Copy specialization hole is a known problem:

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.