Я хочу написать некоторый код, который должен быть универсальным для всех разработчиков определенного трейта, но чей точный тип не может быть известен до времени выполнения.
В частности, я хочу применить функцию из PartialOrd
к подмножеству случаев из данного перечисления. Вот базовая версия того, что я пытаюсь реализовать:
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,
}
}
А затем что-то вроде PartialOrd::gt
заменяется на op
.
Поскольку тип T
определяется плечом сопоставления, он не может быть мономорфизирован и, следовательно, не будет компилироваться.
Есть ли способ обойти это, может быть, с какой-то структурой черты/обертки?
Ссылка на игровую площадку здесь, который включает в себя версию выше (которая не компилируется) и подход к макросам, который я сейчас использую, чтобы обойти это. Макрос работает нормально, но кажется, что это должно быть возможно и без него.
Вы не можете поздно связать универсальную функцию, но вы можете создать для нее трейт:
pub trait Comparator<T> {
fn compare(self, a: &T, b: &T) -> bool;
}
Затем создайте тип для каждого компаратора, например 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)
}
}
Затем:
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,
}
}
Это был ключ! Для потомков, в основном, вот как я это реализовал (используя существующее перечисление Op
вместо трейта): детская площадка. Огромное спасибо!
@erikrhodes Обратите внимание, что рука _ => return false
избыточна, поскольку другие руки уже полностью соответствуют перечислению. (Я бы даже сказал, что рука вредна, как если бы вы позже гипотетически добавили новый вариант перечисления, вы бы хотеть ошибались во время компиляции, которую вы не обработали.)
@cdhowie Отличное замечание - это намеренно сделано в моем реальном коде, где есть другие варианты, которые не соответствуют реализациям PartialOrd
, но здесь совершенно не нужны. Думаю, здесь правильнее всего вернуть Result
и Err
на неподдерживаемые спичечные плечи.
@erikrhodes Да, это имеет смысл, ИМО, поскольку позволяет звонящему решить, паниковать или нет.
Похоже на ту же проблему здесь. Макрос — единственный способ сделать это в настоящее время, поскольку HKT в настоящее время недоступны в Rust.