This is indeed possible, but you need a new trait and a ton of mess.
If you start with the abstraction
enum VecOrScalar<T> {
Scalar(T),
Vector(Vec<T>),
}
use VecOrScalar::*;
You want a way to use the type transformations
T (hidden) -> VecOrScalar<T> -> T (known)
Vec<T> (hidden) -> VecOrScalar<T> -> Vec<T> (known)
because then you can take a "hidden" type T
, wrap it in a VecOrScalar
and extract the real type T
with a match
.
You also want
T (known) -> bool = T::Output
Vec<T> (known) -> Vec<bool> = Vec<T>::Output
but without higher-kinded-types, this is a bit tricky. Instead, you can do
T (known) -> VecOrScalar<T> -> T::Output
Vec<T> (known) -> VecOrScalar<T> -> Vec<T>::Output
if you allow for a branch that can panic.
The trait will thus be
trait FromVecOrScalar<T> {
type Output;
fn put(self) -> VecOrScalar<T>;
fn get(out: VecOrScalar<bool>) -> Self::Output;
}
with implementations
impl<T> FromVecOrScalar<T> for T {
type Output = bool;
fn put(self) -> VecOrScalar<T> {
Scalar(self)
}
fn get(out: VecOrScalar<bool>) -> Self::Output {
match out {
Scalar(val) => val,
Vector(_) => panic!("Wrong output type!"),
}
}
}
impl<T> FromVecOrScalar<T> for Vec<T> {
type Output = Vec<bool>;
fn put(self) -> VecOrScalar<T> {
Vector(self)
}
fn get(out: VecOrScalar<bool>) -> Self::Output {
match out {
Vector(val) => val,
Scalar(_) => panic!("Wrong output type!"),
}
}
}
Your type
#[derive(Copy, Clone)]
struct Clf {
x: f64,
}
will first implement the two branches:
impl Clf {
fn calc_scalar(self, f: f64) -> bool {
f > self.x
}
fn calc_vector(self, v: Vec<f64>) -> Vec<bool> {
v.into_iter().map(|x| self.calc_scalar(x)).collect()
}
}
Then it will dispatch by implementing FnOnce
for T: FromVecOrScalar<f64>
impl<T> FnOnce<(T,)> for Clf
where
T: FromVecOrScalar<f64>,
{
with types
type Output = T::Output;
extern "rust-call" fn call_once(self, (arg,): (T,)) -> T::Output {
The dispatch first boxes the private type up, so you can extract it with the enum
, and then T::get
s the result, to hide it again.
match arg.put() {
Scalar(scalar) => T::get(Scalar(self.calc_scalar(scalar))),
Vector(vector) => T::get(Vector(self.calc_vector(vector))),
}
}
}
Then, success:
fn main() {
let c = Clf { x: 0.0 };
let v = vec![-1.0, 0.5, 1.0];
println!("{}", c(0.5f64));
println!("{:?}", c(v));
}
Since the compiler can see through all of this malarky, it actually compiles away to basically the same assembly as a direct call to the calc_
methods.
That's not to say it's nice to write. Overloading like this is a pain, fragile and most certainly A Bad Idea™. Don't do it, though it's fine to know that you can.