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

Time:04-17

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
}

CodePudding user response:

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.

CodePudding user response:

Well... what is two? For example, if I define my multiplication on Color as mixing two colors, then what does it mean to multiply a color by two?

The easiest way to solve this is by choosing a very small data type that two fits in (such as u8) and accepting any T which can (try to) convert from u8:

fn by_two<T: Mul<Output = T>   TryFrom<u8>>(x: T) -> T {
    x * 2.try_into().ok().expect("could not convert two into T")
}

The more general (but cumbersome) way is to use the num crate, and accept anything that can be multiplied, and has a concept of One, using One One as two.

  • Related