`base` and `inner` - What should I know about the naming convention

I greatly appreciate the many ways in which Rust conveys and enforces an intent. Naming conventions such as unwrap vs into_inner and others (e.g., ad hoc iter) that rely on convention, is great stuff.

So, my question: I noticed in the HashMap module the use of base and inner.

pub struct HashMap<K, V, S = RandomState> {
    base: base::HashMap<K, V, S>,
}

// both values and keys use inner
pub fn values(&self) -> Values<'_, K, V> {
     Values { inner: self.iter() }
    }

I know that Self takes on special meaning and treated accordingly by the compiler. Is there something special to know about how and when to use base and inner?

In the HashMap example, I'm confused re base (other than its scope is <K, V, S> but the keys and values return iterators. Contrast the use of inner with how a "newtype" might use it.

Another opportunity to exploit the convention that comes to mind, is clarifying what to expect when implementing Deref and DerefMut. What is named inner is what might point "by convention" to the "base" attribute of a larger struct/entity... I'm compounding the use of the terms to highlight the reason for my question.

Thanks in advance for any comments.

- E

I don't think there are very strict guidelines about the "base" and "inner" names.

From what I have seen, "inner" may be used in basically any type that wraps another type for any purpose. For cases where the wrapper type is extending or delegating to the inner type, "base" is sometimes used instead, by analogy with a “base class” in classical object-oriented languages.

I guess I’m personally responsible for some of the use of "base" in the std::collections module, so hopefully my understanding is correct. :slight_smile:

3 Likes

The base class idea certainly taps into a lot of implied meaning (perhaps for better or worse in my book :)).

One of the reasons I was reading the modules was to figure out the best way to create an iterator for my struct.

As amazing as the documentation is, the counter example for building an iterator is the wrong use case to introduce the concept. Why? Because the counter is the data. It's confounding a couple of important concepts. The counter hosts the state of the iterator and is the data. Or, is keeping track of an incremented value pointing to nothing. What is the data and what is the lens on that data?

The next place to visit might be Vec... of course a mistake because it's more an exception as much as &[T] has exceptional powers :))

So back to the counter example (pun intended) that fails to introduce the core concept required to tap-into the power of it all: that an iterator is a separate entity/struct with a capacity to "iterate" over your data with move-, borrow- or edit-permitting refs (two entities, one verb and three ways to interact). The data entity can have many different iterators; e.g., skip 3, vs skip 5 (different verbs)... or more a la Map module, different lens (Item). That is where the power is. In my experience, the counter is deceptively simple... for pedagogical purposes, is not necessarily a good thing. It's an example that required that I "un-learn" what I was deceptively led to believe in order to see what is really going on. The tone is not at all my intent, but it helps make the point.

All this to say, this post has me realize how I'm missing a simple recipe for building an iterator without having to think more than what needs be.

Fair? (again, I love the Rust docs)

- E

2 Likes

I see what you mean, yeah. The counter is an iterator, but it's not an iterator over a collection, so it doesn't illustrate how an iterator can use lifetimes to borrow from a collection.

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.