Anonymous Impls
I often find myself writing a function that internally defines a struct just so I can implement a trait on it and return that type as an impl Trait
.
To see what I mean, let's look at a concrete example.
We'll write a function that takes an iterator that yields, for example, 1, 2, 3, 4 and returns a new iterator that yields (1, 2), (3, 4).
/// Assumes the input iterator yields an even number of elements. fn adjacent_pairs<I: Iterator>(xs: I) -> impl Iterator<Item = I::Item> { struct Pairs<I: Iterator> { xs: I, } impl<I: Iterator> Iterator for Pairs<I> { type Item = I::Item; fn next(&mut self) -> Option<Self::Item> { let first = self.xs.next()?; let second = self.xs.next() .expect("source iterator must have even length"); Some((first, second)) } fn size_hint(&self) -> (usize, Option<usize>) { let (size, max) = self.xs.size_hint(); (size / 2, max.map(|max| max / 2)) } } Pairs { xs, } }
There's a mildly annoying amount of ceremony here. We have to define a struct that we construct exactly once. We also have to duplicate the type parameters basically everywhere. It's not the end of the world, but what if we could make this a little shorter?
This is where a potential new Rust feature called anonymous impls could help.
Introducing Anonymous Implsπ
I think the easiest way to introduce anonymous impls is to rewrite our example using them.
/// Assumes the input iterator yields an even number of elements. fn adjacent_pairs<I: Iterator>(xs: I) -> impl Iterator<Item = I::Item> { return impl Iterator { use xs; type Item = I::Item; fn next(&mut self) -> Option<Self::Item> { let first = self.xs.next()?; let second = self.xs.next().expect("source iterator must have even length"); Some((first, second)) } fn size_hint(&self) -> (usize, Option<usize>) { let (size, max) = self.xs.size_hint(); (size / 2, max.map(|max| max / 2)) } } }
The new syntax allows for using impl
in expression position.1
The body mostly looks the same, except we've added a use
line.
This is my proposed syntax for explicit captures.
You might ask why we need explicit captures.
We probably don't strictly need them, but I think it simplifies some things.
The main thing is that methods can have different "modes," as indicated by their self
parameter.
When it comes to captures, it's not clear whether I should just write xs
, or if I should write self.xs
?
If I just write xs
and I'm in the size_hint
method, can I mutate it?
Instead, we can think of use
as pulling a variable from the environment and making it a field on the anonymous impl's Self
type.
Future Possibilitiesπ
The obvious extension to what I've shown so far is to support implementing multiple traits at once.
For the most part you'd just write impl Trait1 + Trait2 { ... }
and implement the union of their methods.
The challenge comes when each trait has a method by the same name.
I suppose in that case we could prefix the method name with the trait name when implementing them, like fn Trait1::foo()
and fn Trait2::foo()
.
At any rate, I think it'd be fine to ship anonymous impls with just one trait at a time.
I'm more excited about how anoynomous impls could interact with future features like associated type position impl trait (ATPIT), associated type inference, and generators.
To see what I mean, suppose we had a trait IntoGen
like this:
trait IntoGen { type Item; type Generator: Gen; // Gen is the trait of generators fn into_gen(self) -> Self::Generator; // note we're putting size_hint here instead of in the `Gen` trait fn size_hint(&self) -> (usize, Option<usize>); }
Then we could rewrite our running example like this:
/// Assumes the input iterator yields an even number of elements. fn adjacent_pairs<I: IntoGen>(xs: I) -> impl IntoGen<Item = I::Item> { let size_hint = xs.size_hint(); let xs = xs.into_gen(); return impl IntoGen { use xs; use size_hint; // Item associated type is inferred // Generator associated type is inferred gen fn into_gen(self) yield I::Item { loop { let first = self.xs.next()?; let second = self.xs.next().expect("source iterator must have even length"); yield (first, second) } } fn size_hint(&self) -> (usize, Option<usize>) { let (size, max) = self.size_hint; (size / 2, max.map(|max| max / 2)) } } }
While I've made a couple of leaps here by combining three new features, hopefully the idea comes across.
The main point is designing the API this way would give us a way to use both gen
syntax while also being able to implement other trait methods like size_hint
.
I think this example is unlikely to be feasible with the current Iterator
/IntoIterator
design.
But if we're willing to tolerate a bit of an impedence mismatch between today's iterators and whatever we do with generators then something like this might become a possibility.
Conclusionπ
I've intended this post as a quick sketch of an anonymous impls feature. If we were to add it, we would get some quality of life improvements immediately. Going further, it could interact synergistically with other features that are likely coming down the pipe.
I prefixed this by return here because otherwise we wouldn't know whether we should be trying to parse impl Iterator {
in item position, as an inherent impl on a type called Iterator
or in expression position as an anonymous impl. There may be better ways to resolve this ambiguity though.β©