Why must a surrounding trait extend Clone for an inner type to be 'really' cloneable?

Can someone explain to me why the following code requires trait A to be bounded by Clone in order to compile? To me it looks like the ound on A::Inner should be sufficient - what am I missing?

fn do_it<A0>(b: &B<A0>) where A0: A {
    let cloned: B<A0> = b.clone();
}

trait A: Clone {
    type Inner: Clone;
}

#[derive(Clone)]
struct B<X> where X: A {
    i: X::Inner,
}

The Clone derive macro naively puts Clone bounds on all type parameters, even if it wouldn't be strictly necessary for the implementation. To fix this, write a manual Clone implementation for B:

impl<X: A> Clone for B<X> {
    fn clone(&self) -> Self {
        Self { i: self.i.clone() }
    }
}
2 Likes

The derived Clone implementation is:

impl<X: Clone> Clone for B<X> where X: A {
    fn clone(&self) -> Self {
        B { i: X::Inner::clone(&self.i) }
    }
}

so when X=A0, B<X> is only clonable if A0 is clonable.

Which mean that even in the original code example, it is not quite true that the

Instead, it suffices to have a A0: Clone bound on do_it, i.e.

fn do_it<A0>(b: &B<A0>) where A0: A + Clone {
    let cloned: B<A0> = b.clone();
}

Of course, the cleaner solution is to fix the Clone implementation as described above.


Depending on what your traits “mean”, in case that implementing the trait is also sensible when A::Inner does not implement Clone, you might even consider not having the type Inner: Clone bound either. This works, too:

fn do_it<A0>(b: &B<A0>)
where
    A0: A,
    A0::Inner: Clone,
{
    let cloned: B<A0> = b.clone();
}

trait A {
    type Inner;
}

struct B<X>
where
    X: A,
{
    i: X::Inner,
}

impl Clone for B<X>
where
    X: A,
    X::Inner: Clone,
{
    fn clone(&self) -> Self {
        Self { i: self.i.clone() }
    }
}

That makes sense, thank you - the derived Clone implementation naively requires the type bound. That is completely reasonable looking at the code, anything else would require an inordinate amount of analysis of the code in order to maybe handle some special cases that could be handled by manually implementing Clone.

Thanks for explaining this. It is one of those things that seem obvious once you see them - but I couldn't figure it out myself.

As for the other suggestions of moving the type bounds to the implementing classes etc - thank you for the ideas. But this example was boiled down from larger real-world code where I feel that having the type bound on the inner type works well.

Thanks everyone for your time and feedback!

Another - arguably even more important - aspect is that with the naive approach of the derive macro, you cannot inadvertently introduce breaking changes to your API by modifying private fields in a way that changes the bounds in the Clone implementation.

2 Likes

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.