Overloadable - Function overloading in rust

In response to this thread, I’ve created this crate.

Sometimes people find themselves wanting to overload functions for simplicity of usage.
With overloadable, we can overload functions using the nightly Fn* traits.

Here is an example of its usage and certain constraints:

use overloadable::overloadable;
use std::fmt::Debug;

overload! {
    //Optional visibility modifier
    pub func as
    //We can specify attributes on each individual function
    #[inline(always)]
    fn(x: usize, y: usize) -> usize {
        x * y
    }
    //The trailing comma is unfortunately required...
    fn<'a,>(x: &'a usize) -> f32 {
        *x as f32
    }
    //We need to put our constraints in the `where` clause and in 
    //square brackets. 
    fn<'a, T>(x: &'a [T]) -> &'a T where [T: std::fmt::Debug] {
        println!("Found {:?}", &x[0]);
        &x[0]
    }
}

In short, it allows you to declare multiple global functions with the same name with at least one of the following being satisfied:

  • They each have a different number of parameters.
  • They each have different types of parameters.
  • They each have their parameters in a different order.

A few limitations are outlined on the docs page.

In the future I might expand this to allow for there to be overloadable functions as part of an impl or such. Any improvements or suggestions are highly appreciated (With the one that bugs me the most being the fact that we need to specify the constraints in the where clause and that those constraints need to be in square brackets).

8 Likes

Well, I couldn't leave it at that (With the ugly syntax and all), so I wrote a proc_macro to do the job.

Now the syntax is very similar to standard rust (y'know, plus the overloading) and has been modified to the following:

use overloadable::overloadable;
use std::fmt::Debug;

overloadable! {
    //Optional visibility modifier
    pub(crate) func as 
    //We can specify attributes on each individual function
    #[inline(always)]
    fn(x: usize, y: usize) -> usize {
        x * y
    }, // <-- Don't forget the comma!
    //Generics are fully supported. 
    fn<'a>(x: &'a usize) -> f32 {
        *x as f32
    },
    //We can now pattern match on items.
    //We can also now use full paths in the constraints
    fn<'a, T>([i1, i2, i3, i4]: &'a [T; 4]) -> &'a T where T: Debug + std::fmt::Display {
        println!("Found {:?} and {} and {:?} and {}", i1, i2, i3, i4);
        i1
    } // <-- Comma not required at the end
}

Hence, overloading 0.3.1!

Add the following to your cargo.toml to start using it now:

overloadable = "0.3.1"
4 Likes

Overloadable 0.4.1 is now out and supports adding overloadable methods to types in a similar fashion to standalone methods:

#[derive(Clone)]
enum Foo {
    A,
    B,
}
overloadable::overloadable_member! {
    Foo::my_func as
    fn(&self) -> &'static str {
        match self {
           Foo::A => "A",
           Foo::B => "B",
        }
    },
    fn(self, x: usize) -> Vec<Self> {
        let mut val = Vec::new();
        val.resize_with(x, || self.clone());
        val
    },
    fn() -> Box<Self> {
        Box::new(Foo::A)
    },
    fn(self: Box<Self>, other: Box<Self>) -> usize {
        match (&*self, &*other) {
            (Foo::A, Foo::A) => 2,
            (Foo::B, Foo::A) | (Foo::A, Foo::B) => 1,
            _ => 0
        }
    }
}

There are fewer restrictions on member functions than non-member functions in that you can have an unconstrained type parameter unlike standalone functions.

The downside to this is that you need to pull in the hidden traits generated by this macro to have them in scope, but since you (as the user) cannot name them, they must all be pulled in via ::*.

3 Likes