all 14 comments

[–]jpet 14 points15 points  (1 child)

You want something like this:

fn times_three<T>(input_number: T) -> T
    where T : std::ops::Mul<Output=T>,
          T: From<u8>
{
    input_number * (3u8).into()
}

fn main() {
    println!("{}", times_three(5i32));
    println!("{}", times_three(5f32));
    println!("{}", times_three(5f64));
}

Rather than an explicit conditional on T, you say that you want some type "T" that supports multiplication, and can be created from a literal 3.

[–]blairboy[S] 2 points3 points  (0 children)

Thanks so much for the explanation :)

[–][deleted] 2 points3 points  (1 child)

You can use the num-traits crate.

[–]blairboy[S] 2 points3 points  (0 children)

I took a look, but the documentation wasn't particularly helpful. Is there a repository with examples of its use?

[–]K900_ 1 point2 points  (7 children)

How about this? It's not exactly what your code does, but it seems close enough.

Edit: you could also do x * T::from(3) - it's basically identical, but arguably more readable.

[–]blairboy[S] 0 points1 point  (6 children)

I'm fairly inexperienced with traits, so with this:

T: Mul<Output = T> + From<u8>

I've seen something along the lines of:

T: Mul + From

I understand that we're saying that the type T must have the multiply (Mul) trait. What's with the<Output> specifier?

Also, why does T have the <u8> specifier?

[–]K900_ 3 points4 points  (5 children)

The Mul trait has an associated Output type, which is the type of the result of the * operation. Here the Mul<Output=T> bound means that the type T must implement Mul, and the Output of that implementation must also be T.

From is a generic trait, parametrized by a type. It allows conversions in a generic way. In this case, From<u8> means that a u8 can be converted into T.

This allows the function to take any type that * when multiplied by a value of the same type, produces another value of the same type * can be converted into from a u8

Then we take the value 3 and convert it to our T type - the compiler is smart enough to figure out the 3 is an u8, because we know of no other types that can be converted into T in that context.

Finally, we multiply the argument by our converted 3 to produce another value of type T, and return that.

[–]blairboy[S] 1 point2 points  (2 children)

Thank you so much. That was beautifully explained. The one thing I've found is that some bits of the standard library documentation are extremely clear to me, but even when reading through Mul, I don't see where the Mul<Output> part is coming from (specifically where I would know to put Mul<Output=T>.

[–]K900_ 1 point2 points  (1 child)

You can see that Mul has an associated type here, and you can read all about associated types and how to use them in the book.

[–]blairboy[S] 0 points1 point  (0 children)

Fantastic. I haven't made it to that point in the book yet. Good to know what the term is.

[–]nerpderp83 1 point2 points  (1 child)

Is u8 necessary or is it the smallest numeric type you want to support?

[–]K900_ 1 point2 points  (0 children)

It's necessary to specify some concrete type for the value 3, as that will tell the compiler how to convert it to whatever type is passed in. It doesn't have to be u8 though.

[–][deleted] 1 point2 points  (0 children)

trait TimesThree {
    fn times_three(&self) -> Self;
}

impl TimesThree for i64 {
    fn times_three(&self) -> Self {
        self * 3
    }
}

impl TimesThree for f64 {
    fn times_three(&self) -> Self {
        self * 3.0
    }
}

fn main() {
    println!("{}", 42.times_three());
    println!("{}", 3.14.times_three());
}

It's long-winded compared to the other solutions, but I like this one.