How to implement Clone and friends for a Box with a reference?

This is my current problem:

/// The internal operations are simple and cheap, but users of the library
/// can create custom expensive operations.
pub trait Operation<Element> {
    /// Since Element could be something big and complex, here it is better to
    /// use always a reference instead of passing the value.
    fn operator(&mut self, element: &Element);
}

/// An element could be a number or other types that could be used for the operations.
pub trait Element {}

/// Enum to allow storing any operation.
/// Since this enum is used everywhere, it needs to support Clone and other traits.
#[derive(Clone, PartialEq, Eq)]
pub enum Operations {
    Sum(CheapSum),
    Product(CheapProduct),
    /// This variant is only for the users of the library, so they can create their
    /// own operations.
    Custom(Box<dyn for<'a> Operation<Box<&'a dyn Element>>>),
}

#[derive(Clone, PartialEq, Eq)]
pub struct CheapSum;

impl<T: Element> Operation<T> for CheapSum {
    fn operator(&mut self, element: &T) {
        todo!()
    }
}

#[derive(Clone, PartialEq, Eq)]
pub struct CheapProduct;

impl<T: Element> Operation<T> for CheapProduct {
    fn operator(&mut self, element: &T) {
        todo!()
    }
}

// Doesn't work
// impl<T: Element> Clone for Box<dyn for<'a> Operation<&'a T>> {
//     fn clone(&self) -> Self {
//         todo!()
//     }
// }
//
// impl<T: Element> PartialEq for Box<dyn for<'a> Operation<&'a T>> {
//     fn eq(&self, other: &Self) -> bool {
//         todo!()
//     }
// }
//
// impl<T: Element> Eq for Box<dyn for<'a> Operation<&'a T>> {}

// Doesn't work
// trait CloneBox {
//     fn clone_box(&self) -> Box<dyn for<'a> Operation<&'a dyn Element>>;
// }
//
// impl<T> CloneBox for T
// where
//     T: for<'a> Operation<&'a (dyn Element + 'a)> + Clone + 'static,
// {
//     fn clone_box(&self) -> Box<dyn for<'a> Operation<&'a (dyn Element + 'a)> + 'static> {
//         Box::new(self.clone())
//     }
// }

It's not possible to use the Clone trait specifically, for two reasons:

  1. The Operation trait allows being implemented on types that are impossible to clone. Somebody could write impl Operation for &mut Unique, and cloning that would violate Rust's safety guarantees. To forbid implementations on non-cloneable types, you would have to define it as pub trait Operation<Element>: Clone

  2. But the Clone trait returns the cloned type by value (-> Self), so traits that require Clone bound also require being Sized. Being Sized means not allowing dyn Trait. So you can't have Clone implemented and use dyn Operation at the same time. dyn Trait doesn't know what type it has, and Clone needs to know exactly what type it returns (Rust can't return by value a type it doesn't know the size of).

Instead, you can add a custom method to trait Operation that returns a cloned Box:

fn cloned_box(&self) -> Box<dyn Operation<Element>>

and use that in your custom implementation of Clone for Operations.

3 Likes

Thank you @kornel. I've used your proposal with some additions for PartialEq.

My final solution:

use std::any::Any;
use std::fmt::Debug;

/// The internal operations are simple and cheap, but users of the library
/// can create custom expensive operations.
pub trait Operation<T: Element>: DynCompare {
    /// Since Element could be something big and complex, here it is better to
    /// use always a reference instead of passing the value.
    fn operation(&mut self, element: &T);

    fn cloned_box(&self) -> Box<dyn for<'a> Operation<Box<&'a dyn Element>>>;
}

/// Based on https://quinedot.github.io/rust-learning/dyn-trait-eq.html
trait AsDynCompare: Any {
    fn as_any(&self) -> &dyn Any;
    fn as_dyn_compare(&self) -> &dyn DynCompare;
}

// Sized types only
impl<T: Any + DynCompare> AsDynCompare for T {
    fn as_any(&self) -> &dyn Any {
        self
    }
    fn as_dyn_compare(&self) -> &dyn DynCompare {
        self
    }
}

trait DynCompare: AsDynCompare {
    fn dyn_eq(&self, other: &dyn DynCompare) -> bool;
}

impl<T: Any + PartialEq> DynCompare for T {
    fn dyn_eq(&self, other: &dyn DynCompare) -> bool {
        if let Some(other) = other.as_any().downcast_ref::<Self>() {
            self == other
        } else {
            false
        }
    }
}

impl PartialEq<dyn DynCompare> for dyn DynCompare {
    fn eq(&self, other: &dyn DynCompare) -> bool {
        self.dyn_eq(other)
    }
}

/// An element could be a number or other types that could be used for the operations.
pub trait Element: Debug {}

impl<'a> Element for Box<&'a dyn Element> {}

/// Enum to allow storing any operation.
/// Since this enum is used everywhere, it needs to support Clone and other traits.
pub enum Operations {
    Sum(CheapSum),
    Product(CheapProduct),
    /// This variant is only for the users of the library, so they can create their
    /// own operations.
    Custom(Box<dyn for<'a> Operation<Box<&'a dyn Element>>>),
}

impl Clone for Operations {
    fn clone(&self) -> Self {
        match self {
            Self::Sum(op) => Self::Sum(op.clone()),
            Self::Product(op) => Self::Product(op.clone()),
            Self::Custom(op) => Self::Custom(op.cloned_box()),
        }
    }
}

impl PartialEq for Operations {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Sum(a), Self::Sum(b)) => a == b,
            (Self::Product(a), Self::Product(b)) => a == b,
            (Self::Custom(a), Self::Custom(b)) => a.as_dyn_compare() == b.as_dyn_compare(),
            _rest => false,
        }
    }
}

impl Eq for Operations {}

impl<T: Element> Operation<T> for Operations {
    fn operation(&mut self, element: &T) {
        println!("In Operations: {element:?}");
    }

    fn cloned_box(&self) -> Box<dyn for<'a> Operation<Box<&'a dyn Element>>> {
        Box::new(self.clone())
    }
}

#[derive(Clone, PartialEq, Eq)]
pub struct CheapSum;

impl<T: Element> Operation<T> for CheapSum {
    fn operation(&mut self, element: &T) {
        todo!()
    }

    fn cloned_box(&self) -> Box<dyn for<'a> Operation<Box<&'a dyn Element>>> {
        Box::new(self.clone())
    }
}

#[derive(Clone, PartialEq, Eq)]
pub struct CheapProduct;

impl<T: Element> Operation<T> for CheapProduct {
    fn operation(&mut self, element: &T) {
        todo!()
    }

    fn cloned_box(&self) -> Box<dyn for<'a> Operation<Box<&'a dyn Element>>> {
        Box::new(self.clone())
    }
}

#[derive(Clone, PartialEq, Eq)]
struct Custom;

impl<T: Element> Operation<T> for Custom {
    fn operation(&mut self, element: &T) {
        println!("In Custom: {element:?}");
    }

    fn cloned_box(&self) -> Box<dyn for<'a> Operation<Box<&'a dyn Element>>> {
        Box::new(self.clone())
    }
}

impl Element for String {}

fn main() {
    let element = "example".to_owned();

    let mut custom = Custom;
    custom.operation(&element);

    let mut operation_a = Operations::Custom(Box::new(custom.clone()));
    operation_a.operation(&element);

    let operation_b = Operations::Custom(Box::new(custom));
    assert!(operation_a == operation_b);
}

Initial impressions

There are ways to do it,[1] but before tackling that, I think your design may benefit from some refactoring.[2]

    Custom(Box<dyn for<'a> Operation<Box<&'a dyn Element>>>),

That's a weird type. In operator, you're going to end up with a &Box<&dyn Element>>. That's at least three layers of indirection. Maybe you want to just...

pub trait Operation<Element: ?Sized> {
    fn operator(&mut self, element: &Element);
}

// ...
    // If it's ok to require a `'static` bound for the implementor
    // of `Element` (you were already requiring that for the
    // implementor of `Operation`)
    Custom(Box<dyn Operation<dyn Element>),
    // If you don't want to require the `'static` for `Element`
    Custom(Box<dyn for<'a> Operation<dyn Element + 'a>>),

Or maybe there should be another method that takes &dyn Element explicitly, if you're going to require it of all implementors.

Custom operations could have their own trait

OK, I wrote the stuff above and then played around a bit. I came to the conclusion that "custom operators" should have their own trait that works with dyn Element specifically. That way the necessity to work with dyn Element and the other restrictions are only imposed on custom operators, and the implementations become more tractable.

Avoid the need for Clone and PartialEq altogether

Alternatively, you could just avoid the need to add these abilities to Box<dyn ...>.

You could get rid of the Eq requirement by never considering custom operators equal or such. It may be surprising to downstream though.

You could get rid of the Clone requirement by storing an Arc<RwLock<dyn ...>> instead. Or Mutex, but that would introduce additional footguns if you still try to implement transparent Eq (deadlocks/panics when comparing to a clone of one's self).

If you get rid of all the Clone and Eq requirements, there's probably no need for a CustomOperation trait.


  1. it does add the restriction that every implementor needs to be Clone and support Operation for some form of dyn Element ↩︎

  2. There's another example for PartialEq, but that's even more restrictive due to the need to downcast. ↩︎

2 Likes

Thank you @quinedot.

I've updated the code with your proposal, which looks simpler. In fact, the first thing I wrote before getting stuck insisting in the wrong path, was using a new trait for the custom operations.

About avoiding Clone and PartialEq, it's something that I can't change because the Operations enum is used in other containers that requires them, but I'm starting to think that maybe I should try to remove some constraints (I've omitted most of them in the example) because they are not necessary anymore.
About using Arc, Mutex, etc. in this case is not necessary because everything runs on the same thread sequentially.

So, this is the current solution:

use std::any::Any;
use std::fmt::Debug;

/// The internal operations are simple and cheap, but users of the library
/// can create custom expensive operations.
pub trait Operation<T: Element + ?Sized> {
    /// Since Element could be something big and complex, here it is better to
    /// use always a reference instead of passing the value.
    fn operation(&mut self, element: &T);
}

pub trait DynOperation: Any {
    fn dyn_clone(&self) -> Box<dyn CustomOperation>;
    fn dyn_eq(&self, other: &dyn CustomOperation) -> bool;
    fn as_any(&self) -> &dyn Any;
    fn as_dyn_op(&self) -> &dyn DynOperation;
}

impl<T: CustomOperation + Clone + Eq> DynOperation for T {
    fn dyn_clone(&self) -> Box<dyn CustomOperation> {
        Box::new(self.clone())
    }
    fn dyn_eq(&self, other: &dyn CustomOperation) -> bool {
        if let Some(other) = other.as_any().downcast_ref::<Self>() {
            self == other
        } else {
            false
        }
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
    fn as_dyn_op(&self) -> &dyn DynOperation {
        self
    }
}

/// Practically speaking, implementors should be `Clone + Eq + 'static`.
pub trait CustomOperation: DynOperation {
    fn custom_operation(&mut self, element: &dyn Element);
}

impl CustomOperation for Box<dyn CustomOperation + '_> {
    fn custom_operation(&mut self, element: &dyn Element) {
        (**self).custom_operation(element)
    }
}

impl Clone for Box<dyn CustomOperation> {
    fn clone(&self) -> Self {
        (**self).dyn_clone()
    }
}

impl Eq for dyn CustomOperation {}
impl PartialEq for dyn CustomOperation {
    fn eq(&self, other: &dyn CustomOperation) -> bool {
        self.dyn_eq(other)
    }
}

impl<T> Operation<dyn Element> for T
where
    T: CustomOperation,
{
    fn operation(&mut self, element: &dyn Element) {
        (*self).custom_operation(element)
    }
}

/// An element could be a number or other types that could be used for the operations.
pub trait Element: Debug {}

impl<'a> Element for Box<&'a dyn Element> {}

/// Enum to allow storing any operation.
/// Since this enum is used everywhere, it needs to support Clone and other traits.
#[derive(Clone, PartialEq, Eq)]
pub enum Operations {
    Sum(CheapSum),
    Product(CheapProduct),
    /// This variant is only for the users of the library, so they can create their
    /// own operations.
    Custom(Box<dyn CustomOperation>),
}

impl<T: Element> Operation<T> for Operations {
    fn operation(&mut self, element: &T) {
        println!("In Operations: {element:?}");
    }
}

#[derive(Clone, PartialEq, Eq)]
pub struct CheapSum;

impl<T: Element> Operation<T> for CheapSum {
    fn operation(&mut self, element: &T) {
        todo!()
    }
}

#[derive(Clone, PartialEq, Eq)]
pub struct CheapProduct;

impl<T: Element> Operation<T> for CheapProduct {
    fn operation(&mut self, element: &T) {
        todo!()
    }
}

#[derive(Clone, PartialEq, Eq)]
struct Custom;

impl CustomOperation for Custom {
    fn custom_operation(&mut self, element: &dyn Element) {
        println!("In Custom: {element:?}");
    }
}

impl Element for String {}

fn main() {
    let element = "example".to_owned();

    let mut custom = Custom;
    custom.custom_operation(&element);

    let mut operation_a = Operations::Custom(Box::new(custom.clone()));
    operation_a.operation(&element);

    let operation_b = Operations::Custom(Box::new(custom));
    assert!(operation_a == operation_b);
}

Rc<RefCell,_>> would be the single-threaded analog.

1 Like

Thanks again. I haven't considered that yet.

Apart from avoiding Clone and PartialEq, etc., since there is only 1 value, does it have more advantages?

I've used them for interior mutability, but I'm not used to these pointers.

The main other advantage is that it's simpler, I think.

A disadvantage is the "PartialEq for custom operations doesn't do what you might expect" aspect.

1 Like

Ok.

I may give it a chance to simplify other parts, but I think that I'm quite happy with the current solution and I wouldn't like to add more overhead to this section, which is probably going to be executed for thousands or millions of operations.