1

I am trying to wrap my head around generics in Rust. To do so, I decided to implement Add generically over a struct I created. I noticed that I kept having to repeat the same where constraints over and over. I also noticed that I had to implement clone in my code despite the fact that T should be able to handle that, but that's because I don't think I know the right way to handle lifetimes in generics yet. Anyway, I would like to name the where constraints so that I don't have to keep copy/pasting them throughout my code.

Can I give the constraints a name?

use std::cmp::PartialOrd;
use std::ops::Add;

struct Thing<T>
where
    T: Add<Output = T> + PartialOrd + Clone, // <-- I would like to give this a name
{
    item: T,
}

impl<T> Thing<T>
where
    T: Add<Output = T> + PartialOrd + Clone,
{
    pub fn new(item: T) -> Self {
        Self { item }
    }
}

impl<T> Add for Thing<T>
where
    T: Add<Output = T> + PartialOrd + Clone,
{
    type Output = Self;
    fn add(self, rhs: Thing<T>) -> Self {
        let left = self.item;
        let right = rhs.item;
        let result = left + right;
        Self::new(result)
    }
}

impl<'a, T> Add<&'a Thing<T>> for Thing<T>
where
    T: Add<Output = T> + PartialOrd + Clone,
{
    type Output = Thing<T>;

    fn add(self, rhs: &Thing<T>) -> Thing<T> {
        let left = self.item;
        let right = rhs.item.clone();
        let result = left + right;
        Thing::new(result)
    }
}

impl<'a, T> Add<Thing<T>> for &'a Thing<T>
where
    T: Add<Output = T> + PartialOrd + Clone,
{
    type Output = Thing<T>;

    fn add(self, rhs: Thing<T>) -> Thing<T> {
        let left = self.item.clone();
        let right = rhs.item;
        let result = left + right;
        Thing::new(result)
    }
}

impl<'a, 'b, T> Add<&'b Thing<T>> for &'a Thing<T>
where
    T: Add<Output = T> + PartialOrd + Clone,
{
    type Output = Thing<T>;

    fn add(self, rhs: &Thing<T>) -> Thing<T> {
        let left = self.item.clone();
        let right = rhs.item.clone();
        let result = left + right;
        Thing::new(result)
    }
}

fn main() {
    let foo = Thing::new(1);
    let bar = Thing::new(2);
    let baz = &foo + &bar;
    let biz = foo + &bar + baz;
    println!("{:?}", biz.item);
}

Solution

Brian helped me answer my question by pointing me to a related question. From that I came up with the following solution to name my trait bounds.

use std::cmp::PartialOrd;
use std::ops::Add;

trait ICanAdd<T>: Add<Output = T> + PartialOrd + Clone {}
impl<T: Add<Output = T> + PartialOrd + Clone> ICanAdd<T> for T {}

struct Thing<T>
where
    T: ICanAdd<T>,
{
    item: T,
}

impl<T> Thing<T>
where
    T: ICanAdd<T>,
{
    pub fn new(item: T) -> Self {
        Self { item }
    }
}

impl<T> Add for Thing<T>
where
    T: ICanAdd<T>,
{
    type Output = Self;
    fn add(self, rhs: Thing<T>) -> Self {
        let left = self.item;
        let right = rhs.item;
        let result = left + right;
        Self::new(result)
    }
}

impl<'a, T> Add<&'a Thing<T>> for Thing<T>
where
    T: ICanAdd<T>,
{
    type Output = Thing<T>;

    fn add(self, rhs: &Thing<T>) -> Thing<T> {
        let left = self.item;
        let right = rhs.item.clone();
        let result = left + right;
        Thing::new(result)
    }
}

impl<'a, T> Add<Thing<T>> for &'a Thing<T>
where
    T: ICanAdd<T>,
{
    type Output = Thing<T>;

    fn add(self, rhs: Thing<T>) -> Thing<T> {
        let left = self.item.clone();
        let right = rhs.item;
        let result = left + right;
        Thing::new(result)
    }
}

impl<'a, 'b, T> Add<&'b Thing<T>> for &'a Thing<T>
where
    T: ICanAdd<T>,
{
    type Output = Thing<T>;

    fn add(self, rhs: &Thing<T>) -> Thing<T> {
        let left = self.item.clone();
        let right = rhs.item.clone();
        let result = left + right;
        Thing::new(result)
    }
}

fn main() {
    let foo = Thing::new(1);
    let bar = Thing::new(2);
    let baz = &foo + &bar;
    let biz = foo + &bar + baz;
    println!("{:?}", biz.item);
}

mpls
  • 395
  • 2
  • 6
  • 3
    FWIW, the proper name for those constraints are "trait bounds". – Brian61354270 Nov 03 '21 at 02:26
  • 3
    [Please don't edit your question to include the answer. Instead you can write a "normal" answer to your question.](https://meta.stackoverflow.com/questions/262806/is-it-ok-for-users-to-edit-the-accepted-answer-into-their-question) – Jmb Nov 03 '21 at 07:57
  • @Jmb Thanks for pointing that best practice. After I accepted Brian's answer, I could not figure out how to add answers to my question. I plan to leave the solution as is, but I'll make sure to put solutions in the answer section before closing out a question in the future. – mpls Nov 03 '21 at 15:39

0 Answers0