Musings on iterator trait names
— 2025-01-20

  1. verbs, nouns, and traits
  2. a verb for iteration
  3. collecting items
  4. async
  5. conclusion

At the end of my last post I mentioned that one of the main issues with the IntoIterator trait is that it’s kind of a pain to write. I wasn’t around when it was first introduced, but it’s not hard to tell that the original authors intended for Iterator to be the primary interface with IntoIterator being an additional convenience.

This didn’t quite turn out to be the case though, and it’s common practice to use IntoIterator in both bounds and impls. In the Rust 2024 edition we’re changing Rust’s range type to implement IntoIterator rather than Iterator. 1 And for example in Swift the equivalent trait to IntoIterator (Sequence) is the primary interface used for iteration. With the interface equivalent to Iterator (IteratorProtocol) having a much harder to use name.

1

Thanks to Lukas Wirth for pointing out that the range type change didn't end up making the cut for the edition. It's been a couple of months since I checked, and it seems it was removed for this edition. My understanding is that this change is still desired, and might make it in for a future edition.

So if not Iterator, what name could we use? Well, recently I wrote a little library called Iterate that tries to answer that question. Let me walk you through it.

Note: this post is intended to be a public exploration, not a concrete proposal. It's a starting point, asking: "... what if?" I'm a firm advocate for sharing ideas in public, especially if they're not yet fully fleshed out. There are a lot of good reasons to do that, but above all else: I think it's fun!

verbs, nouns, and traits

In Rust most interfaces use verbs as their names. To read bytes from a stream you use the trait named Read. To write bytes you use Write. To debug something you use Debug. And to calculate numbers you can use Add, Mul (multiply), or Sub (subtract). Most traits in the Rust stdlib are used to perform concrete operations with, and the convention is to use verbs for that.

The stdlib does have one particularly interesting pairing in the form of Hash (verb) and Hasher (noun). From the documentation: "Types implementing Hash are able to be hashed with an instance of Hasher." Or put differently: the trait Hash represents the operation and the trait Hasher represents the state.

/// A hashable type.
pub trait Hash {
    fn hash<H: Hasher>(&self, state: &mut H);
}

/// Represents state that is changed while hashing data.
pub trait Hasher {
    fn finish(&self) -> u64;
    fn write(&mut self, bytes: &[u8]);
}

A verb for iteration

What the trait IntoIterator really represents is: "An iterable type". And the trait Iterator can be reasonably described as: "The state that is changed while iterating over items". The verb/noun split present in Hash/Hasher feels like it easily applies to iteration too.

If Iterator is the noun that represents the iteration state, what is the verb that represents the capability? The obvious choice would be Iterate. Which I think ends up working out somewhat nicely. To iterate over items you implement Iterate, which provides you with a stateful Iterator.

/// An iterable type.
pub trait Iterate {
    type Item;
    type Iterator: Iterator<Item = Self::Item>;
    fn iterate(self) -> Self::Iterator;
}

/// Represents state that is changed while iterating.
pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

With our goal to make IntoIterator less jarring to use in interfaces, the name Iterate doesn't seem half-bad. And it neatly follows the existing verb/noun split pairing we're already using elsewhere in the stdlib.

Collecting items

People familiar with Rust's stdlib will be quick to note that Iterator and IntoIterator are not the only iteration traits in use. We also have FromIterator that functions as the inverse of IntoIterator. Where one exists to convert types to iterators, the other exists to convert iterators back to types. The latter is typically used via the Iterator::collect function.

But IntoIterator has a less-known but equally useful sibling: Extend. Where IntoIterator collects items into new instances of types, the Extend trait is used to collect items into existing instances of types. It would feel pretty weird to rename IntoIterator to Iterate, but then keep FromIterator as-is. What if instead of anchoring FromIterator as a dual to IntoIterator, we instead treated it as a sibling to Extend. The obvious verb for this would be Collect:

/// Creates a collection with the contents of an iterator.
pub trait Collect<A>: Sized {
    fn collect<T>(iter: T) -> Self
    where
        T: Iterate<Item = A>;
}

It's interesting to note that the type T in FromIterator is bound by IntoIterator rather than Iterator. Being able to use T: Iterate as a bound here definitely feels a little nicer. And speaking of nicer: this would also make the provided Iterator::collect and Iterator::collect_into methods feel a little better:

/// Represents state that is changed while iterating.
pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;

    /// Create a collection with the contents of this iterator.
    fn collect<C>(self) -> C
    where
        C: Collect<Self::Item>,
        Self: Sized,

    /// Extend a collection with the contents of this iterator.
    fn collect_into<E>(self, collection: &mut E) -> &mut E
    where
        E: Extend<Self::Item>,
        Self: Sized;
}

I don't think this looks half-bad. And honestly: it might also be more consistent overall, as traits representing other effects don't have an equivalent to FromIterator. The Future trait only has IntoFuture, and variations on that in the ecosystem like Race. No longer having a trait called FromIterator would help remove some confusion.

Async

I guess we've broached the async topic now, so I guess we might as well keep going. We added the trait IntoFuture to Rust in 2022 because we wanted an equivalent to IntoIterator but for the async effect. You can find some motivating use cases for this in my async builders (2019) post. We chose the name IntoFuture because it matched the existing convention set by IntoIterator/Iterator.

We already have Try for fallibility, we just discussed using Iterate for iteration, what would the verb-based trait name be for asynchrony? The obvious choice would be something like Await, as that is the name of the operation:

trait Await {
    type Output;
    type Future = Future<Output = Self::Output>;
    fn await(self) -> Self::Future;
}

This however runs into one major limitation: await is a reserved keyword, which means we can't use it as the name of the method. Which means I'm not actually sure what this trait should be called. With iterators we're lucky that we don't have any shortage of related words: loop, iterate, generate, while, sequence and so on. With async we're a little shorter on words. If anyone has any good ideas for verbs to use here, I'd love to hear suggestions!

Conclusion

TLDR: I really wouldn't mind Iterate being the main interface name for iteration in Rust. That seems like it would be a step up from writing IntoIterator in bounds everywhere. Just by changing a name, without the need for any special new language features.

Now for whether we should make this change:.. maybe? I honestly don't know. It's not just a matter of introducing a simple trait alias either: the method names and associated types are also different and we can't alias those. And I'm not particularly keen for Rust to start dabbling in additional trait hierarchies here either. Iteration is complex enough as it is, more super-traits are not going to make things any simpler here.

//! Renaming and aliasing the trait is not
//! enough, the method names and associated
//! type names would need to be aliased too.
pub trait Iterate { .. }
pub trait IntoIterator = Iterate;

So I think the only way this rename would actually make sense to follow through on is if the process to make changes like these would make that change easy. I don't believe it is today, but I definitely believe we should want it to become easy in the future. It would be nice if we could freely rename traits, methods, and maybe even types across editions without causing any breakage.

Either way though: I had a lot of fun writing this post. If you want to try the Iterate trait yourself today to get a better feel for it - check out the iterate-trait crate. It has everything I've described in this post, as well as iterator combinators like map. Probably don't use if for anything serious, but definitely go having fun with it.