I want to write some code that should be generic across implementors of a particular trait, but whose exact type can't be known until runtime.
In particular, I want to apply a function from PartialOrd
onto a subset of the cases from a given enum. Here's a basic version of what I'm trying to implement:
enum Value {
String(String),
Float(f64),
Array(Vec<Value>),
}
fn apply_op<T, F>(v: &Value, compare_val: &Value, op: F) -> bool
where
F: FnOnce(&T, &T) -> bool,
T: PartialOrd,
{
match (v, compare_val) {
(Value::String(s), Value::String(compare_str)) => op(s, compare_str),
(Value::Float(f), Value::Float(compare_f)) => op(f, compare_f),
_ => false,
}
}
And then something like PartialOrd::gt
is passed in for op
.
Since the type T
is determined by the match arm, it can't be monomorphized and therefore won't compile.
Is there a way to get around this, maybe with some kind of trait/wrapper struct finagling?
Playground link here, which includes the version above (which doesn't compile) and the macro approach I'm currently using to get around this. The macro works fine, but it really feels like this should be possible without one.
CodePudding user response:
You can't late-bind a generic function, but you can create a trait for that:
pub trait Comparator<T> {
fn compare(self, a: &T, b: &T) -> bool;
}
Then create a type for each comparator, for example Gt
:
struct Gt;
// You can also implement it individually for `String`, `f64` and `Vec<Value>`.
impl<T: PartialOrd> Comparator<T> for Gt {
fn compare(self, a: &T, b: &T) -> bool {
PartialOrd::gt(a, b)
}
}
Then:
fn apply_op<F>(v: &Value, compare_val: &Value, op: F) -> bool
where
F: Comparator<String> Comparator<f64> Comparator<Vec<Value>>,
{
match (v, compare_val) {
(Value::String(s), Value::String(compare_str)) => op.compare(s, compare_str),
(Value::Float(f), Value::Float(compare_f)) => op.compare(f, compare_f),
_ => false,
}
}