Why does Rust require a generic parameter to implement the derived trait in an impl block?

Hello, everyone!

I have a situation where I need to pass a generic parameter of some kind of function defined as a trait. Not a simple Fn or similar traits, but something specific. Here's my minimal code example that reproduces the problem (playground):

pub trait Hasher {
    fn hash(&self) -> u64;
}

#[derive(Debug, Clone)]
pub struct Tree<H> {
    data: Vec<u64>,
    _phantom: std::marker::PhantomData<fn() -> H>,
}

#[derive(Debug, Clone)]
pub struct Node<'a, H> {
    tree_ref: &'a Tree<H>,
    index: usize,
}

#[derive(Debug, Clone)]
pub struct Proof<'a, H> {
    node: Node<'a, H>,
    siblings: Vec<u64>,
}

impl<'a, H: Hasher> Proof<'a, H> {
    pub fn do_something(node: Node<H>) -> Self {
        let n = node.clone();
        todo!()
    }
}

fn main() {
}

The problem is that the Proof impl block there doesn't compile. It says that the clone doesn't work because H doesn't implement Clone. So if I change that impl to impl<'a, H: Hasher + Clone>, it compiles. But... this is wrong! The Hasher shouldn't have a clone functionality (other traits, like Eq and PartialEq, Ord, PartialOrd, etc, also cause similar problems).

Also, another solution is, if I implement Clone manually for Node, then it will also work. But that's very clunky it reminds me of C++ operator== issues, because then no derive works! I have to do this manually for every member for Eq, Clone, PartialEq, and everything else.

What I'd like to have is just derive and have the derived impls just work as if I derive them, so that I wouldn't have to implement them myself or have to add incorrect trait bounds to H.

Why is this problem happening? Can someone please explain what's going on?

Hey @LostPhysicist , I'm fearly new to Rust myself so I'm not sure, but I believe the problem is that you're Deriving the Clone trait in your Proof struct. One of the requirements to deriving Clone, as well as Eq, is that all types inside the struct must also implement those traits.

This trait can be used with #[derive] if all fields are Clone. The derived implementation of Clone calls clone on each field.

Sorry. You're mistaken. If what you said is right, the code would compile when we remove Clone from Proof. But that doesn't happen. Besides, all the members of proof do implement Clone. Node does implement Clone, it's explicitly there, and Vec<u64> implements Clone too since the generic type in it implements Clone too (the u64).

This appears to be a limitation of the clone derive proc macro.

See:

And also:

2 Likes

To answer the question of "why", see this article. Or in summary,

  • Just how derive historically works, instead of "perfect derive" based on each field
  • Tricky to soundly implement perfect derive
  • Less semver hazards than perfect derive
4 Likes

So I gather there's no solution other than implementing this manually by hand, or writing my own macro for every trait I need to derive.

Thank you for the answers.

That's right -- and for PhantomData use this is often actually a good thing, since for example PhantomData is always copy, but it's often the case that something with fields usize & PhantomData<T> shouldn't be Copy. (Like all of Vec's conceptual fields -- a raw pointer, two usizes, and a PhantomData -- are Copy, but the Vec it self better not be.)

It's less good for things like Option<T>, where that's Default regardless of T.

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.