I have a struct which holds values and I need two different implementation which will differently operate on them (conjunctive vs. disjunctive). How can I do this? In a OOP language I would create a base class and extend them with different child-classes which implement different behaviour. How can I do that in Rust?
Can you expand a bit on your scenario? A sketch in pseudocode (or a concrete language) would be good.
Without knowing the details, you could implement this as your struct taking a closure that provides the behavior. Alternatively, you could define a trait that would serve a similar role to the closure.
I have this code:
trait Constraint {
fn matches(&self, other: &Self) -> bool;
}
struct ComplexConstraint {
constraints: Vec<Constraint>
}
And I want a Disjunctive and a Conjunctive Complext-Constraint implementation/type which implement "matches" differently.
You can do one of several things here:
- Pass a flag (eg enum) to
ComplexConstraint
that toggles its behavior. - Make
ComplexConstraint
generic on some type (closure or your own trait) that does the combining. Alternatively, don’t make it generic but use a boxed closure/trait object to encapsulate the behavior.
There are a few more flavors of generics-based solutions but they’re just twists on #2 above. #1 is the simplest and most straightforward. You can probably just fold
over the Vec differently (conjunctive vs disjunctive).
As an aside, the Constraint
trait as defined above will not be object safe - you won’t be able to create trait objects out of it, which looks like what you’ll be aiming to do. You’ll need to replace other: &Self
with other: &Constraint
or rethink the API/design.
Why? I get it, thanks. I've solved it by using an enum:
pub trait Constraint {
fn to_variant(&self) -> &Constraints;
fn matches(&self, provider: &Constraint) -> bool;
}
pub struct ConstraintComposite {
constraints: Vec<Box<Constraint>>
}
impl ConstraintComposite {
pub fn new(constraints: Vec<Box<Constraint>>) -> Self {
Self { constraints }
}
fn conjunctive_match(&self, provider: &Constraint) -> bool {
for constraint in &self.constraints {
if !constraint.matches(provider) {
return false;
}
}
true
}
fn disjunctive_match(&self, provider: &Constraint) -> bool {
for constraint in &self.constraints {
if constraint.matches(provider) {
return true;
}
}
false
}
}
pub enum Constraints {
Single(&'static expr),
Conjunctive(ConstraintComposite),
Disjunctive(ConstraintComposite)
}
impl Constraint for Constraints {
fn to_variant(&self) -> &Constraints {
self
}
fn matches(&self, provider: &Constraint) -> bool {
use self::Constraints::*;
match *self {
Single(expr1) => {
match *provider.to_variant() {
Single(expr2) => expr1 == expr2,
_ => provider.matches(self)
}
}
Conjunctive(ref complex) => complex.conjunctive_match(provider),
Disjunctive(ref complex) => complex.disjunctive_match(provider),
}
}
}
But I'm a bit unhappy about the to_variant method..
You don’t need the Constraints
enum. Here’s the straightforward way i was thinking:
enum FoldType {
Conjunction,
Disjunction,
}
struct CompositeConstraint {
constraints: Vec<Box<Constraint>>,
fold_type: FoldType,
}
impl CompositeConstraint {
fn matches(&self, provider: &Constraint) -> bool {
match self.fold_type {
FoldType::Conjunction => self.constraints.iter().all(|c| c.matches(provider)),
FoldType::Disjunction => self.constraints.iter().any(|c| c.matches(provider)),
}
}
}
Thanks. But as you can see, I have to decide in Constraint::Single, if the other Constraint is also Single or Complex. How could I achieve that without relying on an enum?
I’m not sure why you need to do it that way - is there a reason you cannot rely on the dynamic behavior of matches(...)
?
I need to access the Expression stored inside constraint::Single to compare them.
So maybe matches()
should take an expression and not a trait object?
Then I cannot compare two Constraints that easily, can I?
To be more specific: I must know if the two Constraints are both Singles. If so, I can compare them directly. If not, I switch the comparision. So in other words, I must know the Type of the given Constraint (provider). In Java we solved it with instanceof. That way I've used the to_variant method, which I'm unhappy with.
Ok, I wasn’t sure how this all interacted. I get it now.
So do you need the Constraint trait at all then? It sounds like you can have an enum with two variants (pseudocode): Single(expr) and Composite(Vec<Box<Enum>>
, FoldType).
I need an interface, so that I can match them with each other.
The "interface" would be the enum itself. To match things, you need a type and data - the enum would encapsulate both. It's sort of like your instanceof check in Java except the concrete types are the two enum variants and the "interface" is the enum type. You basically wrote that version above already with the Constraints
enum, except I don't think you need 3 variants (the Composite
variant can be constructed with the fold type rather than baking that in statically). I don't see what the Constraint
trait buys you though.
Well, I would prefer to separate Conjunctive and Disjunctive in different types. But apart from that, is this what you thought of?
pub enum Constraint {
Single(&'static str),
Conjunctive(Vec<Constraint>),
Disjunctive(Vec<Constraint>)
}
impl Constraint {
fn matches(&self, other: &Self) -> bool {
use self::Constraint::*;
match *self {
Single(e1) => {
match *other {
Single(e2) => e1 == e2,
_ => other.matches(self)
}
}
Conjunctive(ref constraints) => constraints.iter().all(|c| c.matches(other)),
Disjunctive(ref constraints) => constraints.iter().any(|c| c.matches(other)),
}
}
}
Yeah, something like that.