I'm struggling to get a good abstraction for a problem of mine.
What I want:
Some type Condition
that represents a tree of logic operators such as &&
and ||
where the leaves contain some closure that takes something of type T
and returns a bool.
In my use case the T
actually would be two types, called T1
and T2
.
To start I implemented the Condition
type without generic parameters and the closure type was Box<dyn Fn(&T1, &T2) -> bool>
which worked and could easily be used.
Now I thought to myself, why not make it generic? If I find a good abstraction I might have other use cases for it!
So I went and added replaced the occurrences of T1
and T2
in the implementation of Condition
with T
as well as adding the generic parameter (T1,T2)
to all my uses of the type.
The problem I encountered is the following:
Before I could pass &T1
and &T2
to the eval
function (as it had two parameters). Now I have to pass either a &(T1, T2)
or &(&T1,&T2)
and both suffer some problems: the first requires to move T1
and T2
into a tuple, but I might not own them, the second works but now I have some lifetime in the type (Condition<(&'a T1,&'a T2)>
) which makes it really unhandy to use and syntactically is not the same as before. For example before, the following function works perfectly fine and returns a Condition
without any lifetime bounds whatsoever:
fn some_condition(&self) -> Condition { //condition has no lifetime parameter
condition::condition!(|foo, bar| (foo.val == 0) and (bar.val > 20))
}
After the changes the code basically stays the same, except for a elided lifetime (and the tuple):
fn some_condition(&self) -> Condition { //has an elided lifetime &'a self and Condition<'a>
condition::condition!(|(foo, bar)| (foo.val == 0) and (bar.val > 20))
}
Now, from my understanding Condition
wouldn't need the lifetime at all, as it doesn't actually contain anything of the type T
(similar to how Box<dyn Fn(&T)>
does not require a lifetime specifier). Is there any way how I can get rid of this lifetime?
(The macro expands into the tree of Condition
).
Simplified code for the generic version:
enum Condition<T> {
And {
lhs: Box<Self>,
rhs: Box<Self>,
},
Bool {
closure: Box<dyn Fn(&T) -> bool>,
},
}
impl<T> Condition<T> {
pub fn eval(&self, t: &T) -> bool {
match self {
Self::And { lhs, rhs } => lhs.eval(t) && rhs.eval(t),
Self::Bool { text: _, closure } => closure(t),
}
}
}
Simplified code for the non generic version:
enum Condition {
And {
lhs: Box<Self>,
rhs: Box<Self>,
},
Bool {
closure: Box<dyn Fn(&T1, &T2) -> bool>,
},
}
impl Condition {
pub fn eval(&self, t1: &T1, t2: &T2) -> bool {
match self {
Self::And { lhs, rhs } => lhs.eval(t1, t2) && rhs.eval(t1, t2),
Self::Bool { text: _, closure } => closure(t1, t2),
}
}
}