Any advice on how dealing with specialisation while keeping code generic?

#1

I’m looking for some advice on how to best deal with data structures that are mostly generic, but occasionally need specialised implementations depending on type.

Here’s a toy example of something I’ve been working with:

use std::ops::Add;

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct Point2<T> {
    x: T,
    y: T,
}

impl<T: Add<T, Output = T>> Add for Point2<T> {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        Self { x: self.x + rhs.x, y: self.y + rhs.y }
    }
}

I’d then like to ensure that when dealing with f32 or f64, that no nan values are present.

Ideally I’d be able to add something like the following (which doesn’t compile due to conflict with existing definition):

impl Add for Point2<f32> {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        assert!(!rhs.x.is_nan() && !rhs.y.is_nan());
        Self { x: self.x + rhs.x, y: self.y + rhs.y }
    }
}

The best alternative I could see was to implement the trait for every numeric type, e.g.:

use std::ops::Add;

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct Point2<T> {
    x: T,
    y: T,
}

impl Add for Point2<f32> {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        assert!(!rhs.x.is_nan());
        Self { x: self.x + rhs.x, y: self.y + rhs.y }
    }
}

impl Add for Point2<i32> {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        Self { x: self.x + rhs.x, y: self.y + rhs.y }
    }
}

This looks like quite an implementation burden (i.e. a mostly repeated implementation for every numeric type), where the core of the functionality is the same.

It wouldn’t be too bad with macros, but I’m wondering whether I’m missing a better approach, whether through some language features, or an entirely different design for the above (or whether macros are the generally accepted way to achieve this).

#2

One possibility would be to define a trait containing the is_nan method and then to implement Add for Point2 over that trait.

trait WithIsNan{
  fn is_nan(&self)->bool;
}
impl<T:WithIsNan+Add<T, Output = T> > Add for Point2<T> {
    type Output = Self;

    fn add(self, rhs: Self) -> Self {
        assert!(!rhs.x.is_nan() && !rhs.y.is_nan());
        Self { x: self.x + rhs.x, y: self.y + rhs.y }
    }
}

It could be even worth to consider the unstable feature of auto traits. With them, we can give an implementation for all types and other for some particular ones.

1 Like
closed #3

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.