Casting to a generic type

2019-02-21 11:38发布

问题:

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?

回答1:

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)));
}


回答2:

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.



标签: rust