Newtype pattern for Vec: how to implement iter()

The "newtype":

pub struct FieldCounters(Vec<FieldLevelCounter>);

The implementations that leverage that of the underlying Vec:

impl FieldCounters {
    pub fn new(num: usize) -> Self {
        let mut field_counters = Vec::with_capacity(num);
        field_counters.push(FieldLevelCounter::new());

        FieldCounters(field_counters)
    }
    pub fn len(&self) -> usize {
        self.0.len()
    }
    fn push(&mut self, value: FieldLevelCounter) {
        self.0.push(value);
    }
    fn as_mut(&mut self) -> &mut [FieldLevelCounter] {
        self.0.as_mut()
    }
    // etc. as needed
}

The issue: How to implement the iter() function? For a HashMap it is straightforward; leverage the ad hoc implementation:

    fn iter(&self) -> hash_map::Iter<'_, Level, Count> {
        self.0.iter()
    }

... not so for Vec. The use of slices puts a wrench in the works. It's a great wrench but...

So, what is the way to implement the iter() and iter_mut() for my "newtype" that wraps a Vec?

If you want to return something different from what is in the Vec, you have to build an iterator 'from scratch' and return that. If you look at how hash_map::Iter (or std::slice::Iter) are implemented, you can see how it is done in general.

However, if you're just trying to return an iterator over the Vec's items, you can do the same thing:

impl FieldCounters {
    fn iter(&self) -> std::slice::Iter<'_, FieldLevelCounter> {
        self.0.iter()
    }
    
    fn iter_mut(&mut self) -> std::slice::IterMut<'_, FieldLevelCounter> {
        self.0.iter_mut()
    }
}

You can also use -> impl Iterator<Item=FieldLevelCounter> to achieve the same thing while hiding the fact that the internals use a slice.

Thank you!

Does the implementation of Iterator enable Rust to coerce or otherwise transform as needed when iter is called?

Separate question, why not implement the traits IntoIterator for &'a FieldCounters?

No, what's happening here is Vec<T>: Deref<Target = [T]>, which allows you to call any of [T]'s methods from Vec<T>. This includes iter

You can do that too

You can also implement Deref and perhaps DerefMut yourself, if it's appropriate for users of your newtype to have that sort of access. Then you won't need a boilerplate method wrapper for every [T] method you want to support.

1 Like

I'll second this. Given that you're willing to allow

fn as_mut(&mut self) -> &mut [FieldLevelCounter]

Then you might as well just allow Deref and DerefMut to the underlying slice.

That said, I'm not sure what you're using the FieldCounters type to do differently from a normal Vec -- if push is a pass-through, there's minimal if any invariant maintenance. You could also just consider a free function to help make the cap-n-len-1 instance, or perhaps an extension trait to add additional methods to Vec<FieldLevelCounter>.

1 Like

There is always one reason I prefer to use the "newtype" pattern: use the type system; type synonyms get messy.

If that's initially not enough, i.e., I have just gone ahead and tolerated using type synonyms (kiss), I then always get pulled into the "newtype" pattern when I want to implement my own traits; whether it be as mundane as Display or something like Combine, a monoid pattern I often like to use.

Finally, I enjoy leveraging what I have learned over the years using Haskell where the type class (~ traits) plays an important role in providing flexibility in the otherwise rigid strongly-typed class of languages.

Should I be looking at this issue differently?

With what was all provided in this post, the most efficient way of getting the Vec iteration to work was to wrap the std::slice::Iter and IterMut ad hoc methods.

Implementing the Deref trait is conceptually nice. I will be putting that into my wheelhouse soon. The idea that we can have something like String with all the bells and whistles, boil down to data that is &str is one of the reasons I love Rust... it just makes sense (when I'm not confused of course :))

If you expect to need different trait impls and such, then a newtype is totally reasonable.

Just wanted to make sure you weren't copying the "I subclassed it because I wanted a different constructor" pseudo-pattern from OOP languages.

1 Like

I appreciate the extra question re why?

I still have to work out the best way to go when for instance, I only need to implement existing traits versus my own. If the latter, I can do that for any existing type... that means no need for "newtype".

I was also thinking more about Deref. Right now, loosely speaking, I see it as fn(Object/class) -> data that can be serialized {} type of thing. From there, I can see how it ties well with how to implement Clone or Copy and the limits therein...

My $0.02:

  • $0.01: you can rewrite your new() function as FieldCounters(vec![FieldLevelCounter::new()]) without pushing and mutability and temporary variables.

  • $0.01: I am a big fan of the newtype pattern, but I basically never find myself needing to wrap collections like that. Almost all the time, either my code is generic and collection-agnostic (i.e. I work over Iterators), or when I need a specific collection, I like to encapsulate a bit more of the work into a higher-level abstraction, where I can enforce invariants without needing to create a container newtype.

    In other words, I don't write separate NodeSets and EdgeLists and ThreadPools and DocumentStores myself. Instead, I write a Graph or an PooledExecutor or a MemoryDatabase, and I stuff those types full of good ol' Vecs. This has the benefit of type aliases without actually needing type aliases, because I can just vec.push(), but it also has the benefit of type safety, since the entire abstraction is after all encapsulated in a type, only it's at a higher level.

... So I could benefit from taking my designs one level higher. May you share an example of a PooledExecutor? One of the trappings I find myself in sometimes is too much abstraction and indirection. "Just get on with it!", I tell myself. It feels as such because the more abstract I get, the more I "think" I bump into my limits dealing with the compiler; mostly borrowing-related and lifetime related (of course :)).

Seeing a concrete example might help ground my thinking here.

Quick update re newtype: Implementing Deref and DerefMut seems to be efficient. Going this approach avoids having to wrap the inner type functions I want exposed.

It's not new(), though, it's new(num), calling Vec::with_capacity(num). So assuming that's important, vec! won't help.

+1 to this, though. I might have a Vec<EdgeId> in a Graph (so I can't mix up NodeIds and EdgeIds), but not an EdgeVec that's internally a Vec<usize>.

2 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.