My crate that I'm working on has a bunch of non-generic containers. Each container offers various ways to iterate through it, and according to the API Guidelines, each iterator method should return a type of the same name. Most of the time, I am simply iterating over a standard collection. So I end up writing a lot of this:
use delegate::delegate;
pub struct Processes {
matched: Vec<Process>,
...
}
impl Processes {
pub fn matched(&self) -> Matched {
Matched(self.matched.iter())
}
...
}
pub struct Matched<'a>(slice::Iter<'a, Process>);
impl<'a> Iterator for Matched<'a> {
type Item = &'a Process;
delegate! {
to self.0 {
fn next(&mut self) -> Option<Self::Item>;
fn size_hint(&self) -> (usize, Option<usize>);
}
}
}
I am already using the delegate macro but it still feels like too much boilerplate. Is there a macro that can help even more in this situation?
I don't think this guideline needs to be followed strictly as a rule, unless your use case requires the iterators to be namable, you can just use RPIT to return an opaque Iterator
type:
impl Processes {
pub fn matched(&self) -> impl Iterator<Item = &Process> {
self.matched.iter()
}
}
How much semver flexibility do you really need?
If you're fine with it being a semver break to change from it being a slice iterator, you could always just add a type Matched<'a> = slice::Iter<'a, Process>;
convenience alias, and thus not need to forward anything.
I mostly agree with the others here. I don't think it's actually needed to wrap your types like this. Usually, when I need iterators I just use it as an opaque iterator type as @nerditation suggests. That being said, I've been meaning to try my had at writing macros myself. They've been a bit mystical to me for too long. Here's what I came up with:
use delegate::delegate;
macro_rules! wrapped_iterator {
($struct:ty, $member:ident, $wrapper:ident, $wrapped:ty) => {
impl $struct {
pub fn $member(&self) -> $wrapper {
$wrapper(self.$member.iter())
}
}
pub struct $wrapper<'a>($wrapped);
impl<'a> Iterator for $wrapper<'a> {
type Item = <$wrapped as Iterator>::Item;
delegate! {
to self.0 {
fn next(&mut self) -> Option<Self::Item>;
fn size_hint(&self) -> (usize, Option<usize>);
}
}
}
};
}
pub struct Process;
pub struct Processes {
matched: Vec<Process>,
}
wrapped_iterator! {Processes, matched, Matched, std::slice::Iter<'a, Process>}
the wrapped_iterator!
invocation expands to this code:
impl Processes {
pub fn matched(&self) -> Matched {
Matched(self.matched.iter())
}
}
pub struct Matched<'a>(std::slice::Iter<'a, Process>);
impl<'a> Iterator for Matched<'a> {
type Item = <std::slice::Iter<'a, Process> as Iterator>::Item;
delegate! {
to self.0 {
fn next(&mut self)->Option<Self::Item>;
fn size_hint(&self)->(usize,Option<usize>);
}
}
}
1 Like
Out of curiosity, what does RPIP mean?
oopsie,got a typo, should be RPIT
: Return Position Impl Trait