Hi folks!
I develop a generic constraint library and have some difficulties to achieve a truly generic library. Basically, I have a potentially infinite number of constraints that must implement several traits: Propagator, DeepClone,... In my solver, I store these constraints in a vector which has type: Vec<Box<Propagator+DeepClone+...>>
, since this syntax is not possible, I use a trait erasure, let's call it ErasurePropagator
. If I decomposed operations into several traits, it's because they are independents. The problem is that DeepClone
has a clone method returning a boxed type, but what should be that type?
-
Box<Self>
doesn't work because it makes DeepClone
object-unsafe.
-
Box<DeepClone>
doesn't work because we can not cast it to Box<ErasurePropagator>
(non-scalar cast).
-
Box<PropagatorErasure>
works but it adds unnecessary coupling between all these traits and DeepClone
would not be re-usable in others contexts.
I don't understand why the first solution prevents DeepClone
to be object-safe (Self
is boxed), is there any conflicting requirements? Is it just not implemented yet? Do you have any suggestions to make it work?
A toy example to test things is available here, it implements the first solution.
1 Like
I found a "workaround" that is acceptable. First DeepClone
just returns Self unboxed. Then, I define a BoxedDeepClone
trait and an implementation for each types implementing DeepClone+Propagator+...
. Thus, the traits DeepClone
, Propagator
, ... stay uncoupled and the machinery for storing multi-traits types is just two traits: BoxedDeepClone
and PropagatorErasure
. The code below is taken from my library and simplified, it should give you the main idea:
// The initial problem was to store types implementing several traits.
pub struct Solver<Dom> {
propagators: Vec<Box<PropagatorErasure<Dom> + 'static>>
}
// The traits we want to polymorphically store.
pub trait Propagator
{
fn propagate(&mut self) -> bool;
}
pub trait PropagatorDependencies
{
fn dependencies(&self) -> Vec<(usize)>;
}
pub trait DeepClone<State>
{
fn deep_clone(&self, state: &State) -> Self;
}
// A kind of "hack" for cloning values while being sure they implement all the traits above.
pub trait BoxedDeepClone<D>
{
fn boxed_deep_clone(&self, state: &Vec<SharedVar<D>>) -> Box<PropagatorErasure<D>>;
}
impl<R, D> BoxedDeepClone<D> for R where
R: DeepClone<Vec<SharedVar<D>>>,
R: Propagator,
R: PropagatorDependencies,
R: 'static
{
fn boxed_deep_clone(&self, state: &Vec<SharedVar<D>>) -> Box<PropagatorErasure<D>> {
Box::new(self.deep_clone(state))
}
}
// The trait erasure, because only one trait is accepted in `Box<Trait>`.
pub trait PropagatorErasure<D>:
Propagator
+ PropagatorDependencies
+ BoxedDeepClone<D>
{}
impl<
D,
R: Propagator
+ PropagatorDependencies
+ BoxedDeepClone<D>
> PropagatorErasure<D> for R {}