Why does deriving Clone not work in this case but implementing manually does?

Hello,
why does Rust display an error message for Test1 below, but not for Test2. They both contain inner structs which are Clone, one which derives Clone and one which implements it manually.

I would like to know how Rust derives Clone in a way that causes the error message below, I thought the manual implementation of Outer2 is essentially equivalent to the derived Clone. But it apparently isn't.

use std::sync::Arc;

struct Value;

#[derive(Clone)]
struct Test1(Outer1<Value>);
//           ^^^^^^^^^^^^^
//           The trait bound `Value: std::clone::Clone` is not satisfied.

#[derive(Clone)]
struct Test2(Outer2<Value>);

#[derive(Clone)]
struct Outer1<T>(Arc<T>);

struct Outer2<T>(Arc<T>);

impl<T> Clone for Outer2<T> {
    fn clone(&self) -> Self {
        Outer2(self.0.clone())
    }
}

fn main() {}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `Value: std::clone::Clone` is not satisfied
 --> src/main.rs:6:14
  |
6 | struct Test1(Outer1<Value>);
  |              ^^^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `Value`
  |
  = note: required because of the requirements on the impl of `std::clone::Clone` for `Outer1<Value>`
  = note: required by `std::clone::Clone::clone`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.

Because the Clone derive blindly bind T: Clone even though Arc<T>: Clone no matter what T is. You correctly don't do that. The Clone derive (and the others in std) is kinda hacky and works in the simple case, but in anything more than the simple case it falls over.

2 Likes

I see, thank you.

That definitely explains it, I don't know why I assumed the type parameter would not be touched by the derive.

I think that it shouldn't touch the type parameter unless it needs to, and that the derive should works solely based on the fields, but it is too late to change that now. :frowning:

It's like that way deliberately. Otherwise, you could change that Arc<T> to a T, and this seemingly internal implementation detail would change the public trait bounds in an impl you can't see.

2 Likes

That makes sense


Just a nit that doesn't really matter:
but if you change Arc<T> to T, that could still break code.

// crate A

pub struct Indirect<T>(Arc<T>);

// crate B

struct Foo(crateA::Indirect<Foo>);

Crate B would break due to an implementation change in crate A, if crate A decided to change Arc<T> to T.

1 Like

Maybe it's too Friday, but I can't see why that would break.
Nevermind, I see -- with Arc<T> it allows indirect type recursion in Foo.

Anyway, changing Arc<T> to Box<T> also makes the point about derivation.

3 Likes

Yes. I understand why the choice was made. Looks like it was thought out quite a bit before making the decision, as is typical of the Rust team. :slight_smile:

It's still contentious though:

https://github.com/rust-lang/rust/issues/26925

1 Like

Oh, I completely forgot about that issue, I remember seeing that a while back!

I started to develop an idea on internals, but then I found this:

https://github.com/rust-lang/rfcs/pull/2353

1 Like

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