Avoid code duplication for Clone, etc

#1

Hi all,

Maybe I’m misunderstanding things on traits, but I can’t solve this issue. Because enum variants are not considered as a type, I used the trick with traits to restrict the list of admissible types on a generic struct:

pub trait TraitA {}
impl TraitA for usize {}
impl TraitA for Vec<usize> {}

Because my push fn is specialized for usize and Vec<usize>, does any trait impl using this fn have to be specialized also ? In the following example, the code impl for Clone is the same which is awful. Is there a clue here?

use std::collections::HashMap;

pub trait TraitA {}
impl TraitA for usize {}
impl TraitA for Vec<usize> {}

pub struct S<T, I: TraitA> {
    pub list: Vec<T>,
    pub hmap: HashMap<String, I>,
}

impl<T, I> S<T, I>
where
    I: TraitA,
{
    pub fn new() -> S<T, I> {
        S {
            list: Vec::new(),
            hmap: HashMap::new(),
        }
    }
}

impl<T> S<T, usize> {
    pub fn push(&mut self, e: T) {
        self.list.push(e);
        self.hmap.insert(String::from("scalar"), 0);
    }
}

impl<T> S<T, Vec<usize>> {
    pub fn push(&mut self, e: T) {
        self.list.push(e);
        self.hmap.insert(String::from("vector"), vec![0]);
    }
}

// Doesn't compile
// impl<T: Clone, I> Clone for S<T, I>
// where
//     I: TraitA,
// {
//     fn clone(&self) -> Self {
//         let mut cloned = S::<T, I>::new();

//         for e in &self.list {
//             cloned.push(e.clone());
//         }

//         cloned
//     }
// }

impl<T: Clone> Clone for S<T, usize> {
    fn clone(&self) -> Self {
        let mut cloned = S::<T, usize>::new();

        for e in &self.list {
            cloned.push(e.clone());
        }

        cloned
    }
}

impl<T: Clone> Clone for S<T, Vec<usize>> {
    fn clone(&self) -> Self {
        let mut cloned = S::<T, Vec<usize>>::new();

        for e in &self.list {
            cloned.push(e.clone());
        }

        cloned
    }
}

Thanks a lot for your hints.

#2

Add the ability to create a new instance to TraitA: example

#3

@vitalyd

Thanks for your swift answer. I didn’t want to elaborate too much in my example, but actually the push implementations are different from each other. If they are, how to avoid code duplication for Clone ?

#4

How different are they? Can you push the diffs into the trait?

Worst case, you can probably use a macro to reduce the manual boilerplate.

#5

Not so much. I’ll try your idea, I didn’t really think of such a method. Thank you for pointing out this way of doing!

If they are very different, the only way is to write a macro?

#6

How about using a trait extension for push? Something like this:

pub trait PushExt<T> {
    fn push(&mut self, e: T);
}

impl<T> PushExt<T> for S<T, usize> {
    fn push(&mut self, e: T) {
        self.list.push(e);
        self.hmap.insert(String::from("scalar"), 0);
    }
}

impl<T> PushExt<T> for S<T, Vec<usize>> {
    fn push(&mut self, e: T) {
        self.list.push(e);
        self.hmap.insert(String::from("vector"), vec![0]);
    }
}

impl<T: Clone, I> Clone for S<T, I>
where
    I: TraitA,
    S<T, I>: PushExt<T>,
{
    /* .. */
}

Playground

#7

@dodomorandi

Nice idea. I’ll explore that one. Thanks!