For example I have a simple classifier
struct Clf {
x: f64
}
The classifier returns 0 if the observed value is smaller than x and 1 if bigger than x.
I now want to implement the call operator for this classifier. However, the function should be able to take either a float or a vector as arguments. In case of a vector, the output is a vector of 0 or 1 which has the same size as the input vector. It should work like this
let c = Clf { x : 0 };
let v = vec![-1, 0.5, 1];
println!("{}", c(0.5)); // prints 1
println!("{}", c(v)); // prints [0, 1, 1]
How can I write the
impl Fn for Clf{
extern "rust-call" fn call ...
...
}
in this case?
This is indeed possible, but you need a new trait and a ton of mess.
If you start with the abstraction
You want a way to use the type transformations
because then you can take a "hidden" type
T
, wrap it in aVecOrScalar
and extract the real typeT
with amatch
.You also want
but without HKT this is a bit tricky. Instead, you can do
if you allow for a branch that can panic.
The trait will thus be
with implementations
Your class
will first implement the two branches:
Then it will dispatch by implementing
FnOnce
forT: FromVecOrScalar<f64>
with types
The dispatch first boxes the private type up, so you can extract it with the
enum
, and thenT::get
s the result, to hide it again.Then, success:
Since the compiler can see through all of this malarky, it actually compiles away completely to the basically the same assembly as a direct call to the
calc_
methods.But 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.
The short answer is: You can't. At least it won't work the way you want. I think the best way to show that is to walk through and see what happens, but the general idea is that Rust doesn't support function overloading.
For this example, we will be implementing
FnOnce
, becauseFn
requiresFnMut
which requiresFnOnce
. So, if we were to get this all sorted, we could do it for the other function traits.First, this is unstable, so we need some feature flags
Then, let's do the
impl
for taking anf64
:The arguments to the
Fn
family of traits are supplied via a tuple, so that's the(f64,)
syntax; it's a tuple with just one element.This is all well and good, and we can now do
c(0.5)
, although it will consumec
until we implement the other traits.Now let's do the same thing for
Vec
s:Now, we have a problem. If we try
c(v)
or evenc(0.5)
(which worked before), we get an error about the type of the function not being known. Basically, Rust doesn't support function overloading. But we can still call the functions using ufcs, wherec(0.5)
becomesFnOnce::call_once(c, (0.5,))
.Not knowing your bigger picture, I would want to solve this simply by giving
Clf
two functions like so:Then your use example becomes
I would actually want to make the second function
classify_slice
and take&[f64]
to be a bit more general, then you could still use it with vecs by referencing them:c.classify_slice(&v)
.You can't.
First of all, implementing the
Fn*
family of traits explicitly is unstable and subject to change at any time, so it'd be a bad idea to depend on that.Secondly, and more importantly, the Rust compiler just will not let you call a value that has
Fn*
implementations for different argument types. It just can't work out what you want it to do, since there's normally no way for it to happen. The only way around that is fully specifying the trait you wanted to call, but at that point, you've lost any possible ergonomic benefit of this approach.Just define and implement your own trait instead of trying to use the
Fn*
traits. I took some liberties with the question to avoid/fix questionable aspects.Note: included for the sake of completeness; do not actually do this. Not only is it unsupported, it's damn ugly.