Search code examples
rustinterfacetraits

How to implement the `From` trait for a generic type, and retrieve the internal generic type?


For example I have below generic struct:

use std::convert::From;

pub struct Rect<T> {
  pub top_left: (T, T), // 0 is x, 1 is y.
  pub bottom_right: (T, T), // 0 is x, 1 is y.
}

impl<T> Rect<T> {
  pub fn new(top_left: (T, T), bottom_right: (T, T)) -> Self {
    Rect { top_left, bottom_right }
  }
  pub fn height(&self) -> T {
    self.bottom_right.1 - self.top_left.1
  }
  pub fn width(&self) -> T {
    self.bottom_right.0 - self.top_left.0
  }
}

pub struct Size<T> {
  pub height: T,
  pub width: T,
}

impl<T> Size<T> {
  pub fn new(height: T, width: T) -> Self {
    Size { height, width }
  }
}

impl<T: Rect<H>> From<T> for Size<H> {
  fn from(rect: T) -> Size<H> {
    Size::new(rect.height() as T, rect.width() as T)
  }
}

I had two generic types:

The Rect<T> is a rectangle, it has top-left point and bottom-right point. The T can be isize, usize, i32, etc. The rectangle has a height method and a width method.

The Size<T> is a size of a rectangle, it only as the height and width, no points. The T can be isize, usize, i32, etc.

Apparently, the Rect<T> can be converted into a Size<T>, so I want to implement the From trait.

But I just don't know how to declare the generic parameter T. Please help me.


Solution

  • impl<T: Rect<H>> From<T> for Size<H>
    

    This doesn't work because you cannot bound on types, only on traits (and lifetimes). So T: Rect<H> is invalid.

    Instead, you want to say something like this:

    impl<T, H> From<Rect<T>> for Size<H> {
        fn from(rect: Rect<T>) -> Size<H> {
            Size::new(rect.height(), rect.width())
        }
    }
    

    This won't work either, though, because Size::<H>::new expects you to give it H's but you're giving it T's. The simplest fix would be to say that you can convert a Rect<T> to a Size<T> (the types must match):

    impl<T> From<Rect<T>> for Size<T> {
        fn from(rect: Rect<T>) -> Size<T> {
            Size::new(rect.height(), rect.width())
        }
    }
    

    Alternatively, you can require that T: Into<H> and then convert the height and width using .into():

    impl<T: Into<H>, H> From<Rect<T>> for Size<H> {
        fn from(rect: Rect<T>) -> Size<H> {
            Size::new(rect.height().into(), rect.width().into())
        }
    }
    

    Note also that your Rect::height and Rect::width methods don't compile because there's no guarantee that T implements subtraction. You can fix this by breaking them out into their own impl block which bounds T: std::ops::Sub<Output = T>:

    impl<T: std::ops::Sub<Output = T>> Rect<T> {
        pub fn height(&self) -> T {
            self.bottom_right.1 - self.top_left.1
        }
    
        pub fn width(&self) -> T {
            self.bottom_right.0 - self.top_left.0
        }
    }
    

    Now we have a new problem: the compiler seems to think we're trying to move the T values in the Rect, and indeed we are! The simple fix, if you are only going to use this with numeric primitives, would be to also require T: Copy. This requires that T be a bitwise-copyable value, and so instead of moving the Ts out of the fields of self, they will be copied instead.

    impl<T: std::ops::Sub<Output = T> + Copy> Rect<T> {
    

    (You could instead bound on Clone and then say self.bottom_right.1.clone() - self.top_left.1.clone(), which would support types that can be cloned but not bitwise-copied, such as num_bigint::BigInt.)


    Okay, that's all working... but now the From<Rect<_>> for Size<_> implementation broke. It's calling width and height on the Rect value provided, but we can't because T doesn't meet the bounds for them. All we have to do is copy the bounds we added above:

    impl<T: Into<H> + std::ops::Sub<Output = T> + Copy, H> From<Rect<T>> for Size<H> {
    

    And now everything works!

    (Playground)