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() }
}
}
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.
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.