Learning about traits and trait implementation - possible to avoid repetition?

I'm currently learning about traits in rust by working on a toy library for making shopping lists.

TL;DR: Using Deref to produce iterators out of structs that contain a Vec; want to create a custom trait that implements Deref that then could be implemented on any struct containing a Vec; playground here with summary example code below.

So far my toy grocerylist/recipes library includes structs for Groceries, GroceriesItem, Recipes, and Recipe:

pub struct Groceries {
    pub collection: Vec<GroceriesItem>,
}

pub struct GroceriesItem {
    pub name: GroceriesItemName, // e.g. "apples"
    pub section: GroceriesItemSection, // e.g. "fresh"
    pub is_recipe_ingredient: bool, 
    pub recipes: Recipes, // vec!["apple pie", "cheese plate"]
}

pub struct Recipes {
    pub collection: Vec<Recipe>,
}

pub struct Recipe(pub String);

(Playground with more such code)

I want to explore implementing custom iterators for these types, and I settled on a simplest method first (that I learned about on StackOverflow), using Deref to produce an iterator from the underlying Vec in my Groceries and Recipes data structures.

This involves almost repeating code when I implement Deref for Groceries and Recipes:

impl Deref for Groceries {
    type Target = Vec<GroceriesItem>;

    fn deref(&self) -> &Self::Target {
        &self.collection
    }
}

impl Deref for Recipes {
    type Target = Vec<Recipe>;

    fn deref(&self) -> &Self::Target {
        &self.collection
    }
}

Is there a way to create a custom trait that implements Deref that then could be implemented on any struct containing something called collection (as in my structs here) that is Vec<_>?

On one hand, there's not really any way to make this simpler with another trait as default impls can't access fields, so this seems like the job for a macro.

macro_rules! deref_to_vec {
    ($t:ty, $target:ty) => {
        impl Deref for $t {
            type Target = Vec<$target>;

            fn deref(&self) -> &Self::Target {
                &self.collection
            }
        }
    }
}

deref_to_vec!(Groceries, GroceriesItem);
deref_to_vec!(Recipes, Recipe);

On the other hand, is there any functionality you need Groceries and Recipes to have that Vec doesn't supply? If not, it might be good to just use Vec<GroceriesItem> and Vec<Recipe> instead of custom types.

If there is, Deref still might not be the best option, it may be better to implement Index instead

impl Index<usize> for Groceries {
    type Item = GroceriesItem;
    fn index(&self, idx: usize) -> &Self::Item {
        &self.collection[idx]
    }
}

// .. and `IndexMut`

(or make a get/get_mut function if you want it to return an Option)

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.