Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a vector based on an `&[Box<dyn CustomTrait>]`?

Tags:

rust

I have a custom trait that I use as the element type in a slice:

pub trait IConstraint {
  // members here
}

pub struct Scenario<'a> {
  pub constraints: &'a [Box<dyn IConstraint>]
}

I would like to offer an add_constraint method that does a copy-on-write of the slice. Something like this:

impl<'a> Scenario<'a> {
    pub fn add_constraint(&mut self, constraint: Box<dyn IConstraint<TNodeState>>) {
        let mut constraints: Vec<Box<dyn IConstraint<TNodeState>>> = Vec::new();
        constraints.copy_from_slice(self.constraints);
        constraints.push(constraint);
        self.constraints = &constraints;
    }
}

The problem is I get this error:

the trait bound Box<dyn IConstraint<TNodeState>>: std::marker::Copy is not satisfied the trait std::marker::Copy is not implemented for Box<dyn IConstraint<TNodeState>>

Ok, so the Box<T> doesn't implement the Copy trait. Fair enough. But how do I address that? Ideally, I'd reuse the boxes or at least the constraints because they are immutable. But if I can't do that due to rust ownership rules, how can I implement the Copy trait on a box type? I've tried various ways and they all produce errors.

This attempt produces "Copy not allowed on types with destructors":

impl<TNodeState> Copy for Box<dyn IConstraint<TNodeState>> {
}

What about Clone? I can switch to constraints.clone_from_slice(self.constraints);, but then implementing the Clone trait on IConstraint produces a bunch of "IConstraint cannot be made into an object" errors.

Even if I could get box to be cloneable, then of course I get the anticipated borrow lifetime flaw from my add_constraint method:

borrowed value does not live long enough

So do I have to throw out my add_constraint function idea altogether and force the owner of my struct to manually copy it? Given my actual struct contains 3 fields, that gets tedious as the owner now has to deconstruct the fields into locals in order to drop the immutable borrow, allowing the original vector to be mutated:

fn add_remove_constraint() {
    let mut scenario = Scenario::<&str, bool, 3_usize>::new(&["A", "B", "C"]);
    let mut constraints: Vec<Box<dyn IConstraint<bool>>> = Vec::new();
    constraints.push(Box::new(SelectionCountConstraint {
        nodes: [1, 2],
        min: 1,
        max: 2,
    }));
    scenario = Scenario {
        constraints: &constraints,
        ..scenario
    };

    assert_eq!(1, scenario.get_constraints().len());
    let nodes = scenario.nodes;
    let selection_state = scenario.selection_state;
    constraints.pop();
    scenario = Scenario {
        constraints: &constraints,
        nodes,
        selection_state,
    };
    assert_eq!(0, scenario.get_constraints().len());
}
like image 246
Andrew Arnott Avatar asked Dec 03 '25 04:12

Andrew Arnott


1 Answers

I think you got it all wrong. As said in the comments, your add_constraint will never work, because you are referencing something you create in the same function in the first place (which will be dropped after the function scope expires).

You should own a container over those IConstraint trait, but inside you should have an &dyn IConstraint or Box<dyn IConstraint>.

Adding an item to them is trivial in that case:

pub trait IConstraint {
  // members here
}

pub struct Scenario<'a> {
  pub constraints: Vec<&'a dyn IConstraint>
}

impl<'a> Scenario<'a> {
    pub fn add_constraint(&mut self, constraint: &'a dyn IConstraint) {
        self.constraints.push(constraint);
    }
}

Playground

This should solve your problem since references are Copy.

like image 60
Netwave Avatar answered Dec 05 '25 22:12

Netwave



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!