Musings on iterator trait names
— 2025-01-20
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 1
And for example in Swift the equivalent trait to IntoIterator
rather than Iterator
.IntoIterator
(Sequence
) is
the primary interface used for iteration. With the interface equivalent to
Iterator
(IteratorProtocol
) having a much harder to use name.
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.