Manual Trait Overloading

I devised a simple and efficient scheme to do overloading based on traits.

Suppose that t1 implements Trait1 and Trait2. You can select which trait to use for each argument to a function in the following way:

f(AsTrait1(&t1), AsTrait1(&t1));
f(AsTrait1(&t1), AsTrait2(&t1));

This is useful when the implementation of f can be made more performant, the result more accurate, and so on, depending on the available traits.

The implementation of f looks like this:

pub fn f<T1: AsTrait1Or2, T2: AsTrait1Or2>(x: T1, y: T2) -> FResult {
    // NOTE: It's important to ALWAYS check for exhaustiveness.
    match (T1::TRAIT, T2::TRAIT) {
        (AllTraits::Trait2, _) => FResult::Str(x.t2().method2()),
        (_, AllTraits::Trait2) => FResult::Str(y.t2().method2()),
        (AllTraits::Trait1, AllTraits::Trait1) => FResult::IntInt(
            x.t1().method1(), y.t1().method1()),
    }
}

There's only a single implementation and you can inspect the available traits and behave accordingly without having to worry about performance because T1::TRAIT and T2::TRAIT are associated consts, so the match expression and any related conditional logic are optimized away after monomorphization.

This is cool because you can write the logic in regular Rust, which is much more powerful than any automatic but fixed overloading resolution.

See the proof of concept and detailed presentation to learn more.

1 Like

See also (from 2015) Rust Patterns: Using traits for function overloading | by Jonathan Reem | Medium

1 Like

The method you linked (M2, from 2015) uses traits to emulate overloading, while mine (M1) does trait overloading.

M2 considers each argument separately, as the polymorphic code is limited to the conversions. Once the conversions are done, you're left with monomorphic code.

M1 allows you to write arbitrary compile-time logic involving all the arguments. This makes it more powerful than classical overloading since, in M1, you're expressing the logic using Rust itself.

For instance, you could write a function that takes several arguments and has a way to speed up computation when any argument supports a particular trait. It doesn't matter which argument, or how many arguments support that trait: one is enough.