Search code examples
mathrusttraits

What is the Rust way to deal with constant math when we only have a generic trait


Here is my broken example code. Note that I just want to double the value and allow anything which knows how to multiply.

fn by_two<T: Mul<Output = T>>(x: T) -> T {
    x * 2
}

Solution

  • I think the issue you are running into is you needs to say what you are multiplying with. 2 defaults to an <integer> which if unspecified counts as an i32, so here is that example:

    fn by_two<T: Mul<i32, Output=T>>(x: T) -> T {
        x * 2
    }
    

    Alternatively, you could just leave the result up to the trait. This is generally* equivalent to just using x * 2 without calling the function.

    fn by_two<T: Mul<i32>>(x: T) -> <T as Mul<i32>>::Output {
        x * 2
    }
    

    However, this won't work with most types since Rust usually expects primitives to be multiplied with other privatives of the same type. We could change it to use From<i32> to make it more inclusive.

    fn by_two<T: Mul + From<i32>>(x: T) -> <T as Mul>::Output {
        x * T::from(2)
    }
    

    At this point you may be noticing the problem. We need to figure out what type "2" is so it can multiply with T. The From<i32> solution works well for primitive types, but you will start to run into difficulties if you tried to plug a vector or matrix into the function. One solution would be to add a separate generic to allow the user to specify what it gets multiplied with.

    fn by_two<T: Mul<K>, K: From<i32>>(x: T) -> <T as Mul<K>>::Output {
        x * K::from(2)
    }
    

    However, this is starting to get a bit cumbersome. Unlike the previous versions, we can't let the compiler infer a type to use for K since there might be multiple types which could satisfy the requirements put on K.

    One option which doesn't quite solve our problem, but may help us a bit would be to use a crate like num-traits. Though to be honest, this really just amounts to more or less the same thing as example 2, but with a more generalized concept of 2.

    use num_traits::identities::One;
    fn by_two<T: One + Add<Output=T>>(x: T) -> T {
        x * (T::one() + T::one())
    }
    

    You have a ton of options, but none are all inclusive. If you want to keep your match as generic as possible, you are probably best off writing the function first, then determining what mathematical properties the values must satisfy to make it work. You can use num-traits for basic trait bounds such as differentiating between floats and ints. Then for more complex concepts, I recommend using alga.