Should I write my own derive macro for cloning structs of this nature?

I have this Node type to help with building graphs. In the example below, a user (myself in this case) wants to make a Text node to build graphs to represent text. It could be a common pattern to provide a trait object (dyn TextTrait in this case) but I am struggling to derive Clone for Text (derive(Clone) complains about the trait object). I don't want the user to be required to put their trait object behind smart pointers because I want the Node type to take care of that. So my first solution is to have the user implement Clone for Text but that would be repetitive over each node type. So I think the better solution would be to make a macro so users could do #[derive(CloneNode)]. Is that a good solution or is there a way to make the regular #[derive(Clone)] work?

This could be the x y problem so I'm open to other ideas.

More context:
My goal is to learn how to build graphs that work well with concurrency under Tokio. The user could provide an enum instead of a trait object when designing their node type which would solve the cloning problem but I believe trait objects are appropriate for a lot of cases. Node::meta is cheap to clone and would hold an ID so the content could be recovered from file storage if the RwLock is poisoned. T could be expensive to clone and needs to be read or written to from multiple Tokio tasks so that's why I put it behind Arc and RwLock. Please see the code below.

// Graph crate providing API
#[derive(Clone)]
pub struct Node<T: ?Sized> {
    pub content: Arc<RwLock<T>>,
    pub meta: Meta,
}

// Example user code, struct Text could be different, the point being that is contains 
// a Node<dyn MyTrait> for one of the fields
// #[derive(Clone)] // This complains that dyn TextTrait: Clone trait bound is 
// not satisfied as expected 
pub struct Text(
    pub Node<dyn TextTrait>
);

// Should I write a derive macro to make this?
impl Clone for Text {
    fn clone(&self) -> Self {
        Text(
            Node {
                content: self.0.content.clone(),
                meta: self.0.meta.clone(),
            }
        )
    }
}

Looks like you just need to unconditionally implement Clone for Node<T: ?Sized>?

impl<T: ?Sized> Clone for Node<T> {
    fn clone(&self) -> Self {
        Self {
            content: self.content.clone(),
            meta: self.meta.clone(),
        }
    }
}

In case you weren't aware, the built in derive will do something like

impl<T> Clone for Node<T>
where
    T: ?Sized + Clone,

But you don't need T: Clone since it's wrapped up in an Arc.

1 Like

Wow, this is perfect! I didn't think to make a blanket implementation like that. Thank you so much.

1 Like

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.