2

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());
}
Andrew Arnott
  • 80,040
  • 26
  • 132
  • 171
  • 1
    If you want shared ownership, you probably should reach out for `Rc` – Ivan C May 30 '22 at 16:24
  • 1
    your `add_constrain` method will never work as you are referencing something that is being created in the same function. – Netwave May 30 '22 at 16:43
  • 1
    Nitpick: Hungarian notation (`TParam`, `IInterface`) is not used with Rust usually. – Chayim Friedman May 30 '22 at 18:22
  • 1
    Does this answer your question? [How to clone a struct storing a boxed trait object?](https://stackoverflow.com/questions/30353462/how-to-clone-a-struct-storing-a-boxed-trait-object) – Chayim Friedman May 30 '22 at 18:30

1 Answers1

2

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.

Netwave
  • 40,134
  • 6
  • 50
  • 93
  • Thanks. And yes, I'm _very_ new to rust so I accept the "you got it all wrong" ruling. :) > You should own a container over those IConstraint trait Doesn't that mean that copies of `Scenario` will have to clone the vector? I'm trying to avoid that. I'm not sure how that would even work given `dyn trait` doesn't seem to like to be cloneable. – Andrew Arnott May 30 '22 at 20:42
  • Ah, maybe your bolded ending comment explains it: a `Vec` copies by ref, so no cloning is necessary in that case? I'll try that out. – Andrew Arnott May 30 '22 at 20:45
  • I do find that cloning my Scenario requires that I call `constraints.clone()`. Is that going to be a fast reference copy or will it copy the contents into a new array? I'm hoping for a fast reference copy. – Andrew Arnott May 30 '22 at 21:17
  • @AndrewArnott it will clone just the references. You would have a new vector with references – Netwave May 30 '22 at 21:58
  • I don't want a new vector though. That's another heap allocation and copy of a potentially long vector, even if the elements of the vector are themselves just references. I think I'll try separating the most mutable state (the `nodes` field) from the rest of the fields so that I can do as you suggest but without incurring the shallow copy cost. – Andrew Arnott May 31 '22 at 00:24