15

I have a newbie question about generics in Rust (version 1.0).

Let's say I write a generic function to do division. Never mind the usefulness of such a function; it's a simple function to keep this question simple.

fn divide<T: std::ops::Div>(a: T, b: T) -> T {
    a / b
}

fn main() {
    println!("{}", divide(42, 18))
}

This program fails to compile.

src/main.rs:2:5: 2:10 error: mismatched types:
 expected `T`,
    found `<T as core::ops::Div>::Output`
(expected type parameter,
    found associated type) [E0308]
src/main.rs:2     a / b
                  ^~~~~

I understand that the compiler error is telling me that the result of the division operation is type Output, not T, and I see the Output type in the standard library documentation.

How do I convert from Output to T? I try to use as to cast.

fn divide<T: std::ops::Div>(a: T, b: T) -> T {
    (a / b) as T
}

fn main() {
    println!("{}", divide(42, 18))
}

This causes a different compiler error.

src/main.rs:2:5: 2:17 error: non-scalar cast: `<T as core::ops::Div>::Output` as `T`
src/main.rs:2     (a / b) as T
                  ^~~~~~~~~~~~

I'm out of ideas to make this work, and I realize I lack understanding of something fundamental about the language here, but I don't even know what to look for to make this work. Help?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Craig M. Brandenburg
  • 3,354
  • 5
  • 25
  • 37

2 Answers2

13

You simply have to specify T::Output as the return type of the function:

fn divide<T: std::ops::Div>(a: T, b: T) -> T::Output {
    a / b
}

Edit to add more explanation on why you cannot do the cast inside the function

When you are IN your generic function divide, the compiler yet doesn't know that you can cast T to T::Output, so the cast is invalid. They are generic types, they can be anything, how the compiler knows that you can cast from T to T::Output ?

a / b produces something of type T::Output, so in the solution above there is not cast, T::Output is simply the right type.

Edit to add another possible solution using std::convert::From

The most (I think) generic implementation is when you know that the cast from T::Output to T is possible. You can bound T to implement From for T::Output. This is a complete example:

use std::ops::Div;
use std::convert::From;

fn divide<T: Div>(a: T, b: T) -> T
    where T: From<<T as Div>::Output>
{
    T::from(a / b)
}

#[derive(Debug)]
struct Bip(u32);

impl Div for Bip {
    type Output = f32;

    fn div(self, rhs: Bip) -> f32 {
        (self.0 / rhs.0) as f32
    }
}

impl From<f32> for Bip {
    fn from(value: f32) -> Self {
        Bip(value as u32)
    }
}

fn main() {
    println!("{:?}", divide(12, 4));
    println!("{:?}", divide(Bip(12), Bip(4)));
}
eulerdisk
  • 4,299
  • 1
  • 22
  • 21
  • 1
    That works, but _why_ does it work? If I call `divide` with `T` of type `u32` and assign the returned value to a variable of type `u32` (i.e., `let x: u32 = divide(42u32, 18u32);`) then it compiles. Why is it the return expression inside `divide -> T` cannot convert from `T::Output` to `T`, but when using the returned value outside `divide` the type system allows the conversion (from `u32::Output` to `u32`)? I.e., appending`::Output` to the return type of `divide` moves the conversion from _inside_ the function to _outside_ the function. What's going on here? – Craig M. Brandenburg Jun 20 '15 at 13:31
  • 2
    Because when you are IN your generic function `divide`, the compiler yet doesn't know that you can cast `T` to `T:Output`. (They are generic, they can be anything, even something that cannot cast) `a / b` produces something of type `T::Output`, so in the solution above there is not cast, `T::Output` is simply the right type. – eulerdisk Jun 20 '15 at 14:03
7

Andreas' answer explains why the cast is impossible, and that making the function as generic as possible solves this issue.

It is not, however, the only solution. Rust also supports the ability to constrain the associated types (Output here) of a generic function.

An alternative would be:

use std::ops::Div;

fn divide<T>(a: T, b: T) -> T
    where T: Div<Output = T>
{
    a / b
}

The <Output = T> bit instructs the compiler to only accept in this functions types T for which the implementation of Div has an Output equal to T. This is obviously more restricting, but it allows ensuring that the result of divide is of type T.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 3
    Good point on the `=` constraint. I don't find too much of that in the official docs, can you give us some references ?? However to add even more stuff, there's the case when You can require the `From` trait to be implemented for `T`. (I edited my response again) – eulerdisk Jun 22 '15 at 08:25
  • 1
    @AndreaP: It's alluded to at the very end of the [Associated Types](https://doc.rust-lang.org/book/associated-types.html#trait-objects-with-associated-types) chapter. There is not much to say about them, so the docs don't say much, but I admit it's easy to miss. – Matthieu M. Jun 22 '15 at 09:01