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