3

I've got some code that attempts to run a match where each branch can return a different type, but all of these types implement Iterator<Item=usize>.

let found: Iterator<Item = usize> = match requirements {
    Requirements::A => MatchingAs { ainternals: [] },
    Requirements::B => MatchingBs { binternals: [] },
    Requirements::C => MatchingCs { cinternals: [] },
};

return found.any(|m| m == 1)

... where MatchingAs, MatchingBs, and MatchingCs all impl std::iter::Iterator<Item = usize>.

I'm hitting a wall with the fact that the Iterator isn't sized:

    | the trait `std::marker::Sized` is not implemented for `std::iter::Iterator<Item=usize>`

Is there a good approach to have match arms return objects with a shared trait, and then rely (only) on the trait in processing the results?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Bosh
  • 8,138
  • 11
  • 51
  • 77
  • 1
    Please explain why this is not a duplicate of [Correct way to return an Iterator?](http://stackoverflow.com/q/27535289/155423) – Shepmaster Jan 17 '17 at 18:41

1 Answers1

6

The first reflex when you want to return something that is not Sized, is to Box it (aka, put it on the heap, return a pointer):

let found: Box<Iterator<Item = usize>> = match requirements {
    Requirements::A => Box::new(MatchingAs { ainternals: [] }),
    Requirements::B => Box::new(MatchingBs { binternals: [] }),
    Requirements::C => Box::new(MatchingCs { cinternals: [] }),
};

found.any(|m| m == 1)

This is not sufficient, here, because now match will complain that you return different types: Box<MatchingAs>, Box<MatchingBs>, ...

However, Box<Concrete> can be cast to Box<Trait> whenever there is an impl Trait for Concrete, so:

let found = match requirements {
    Requirements::A => Box::new(MatchingAs { ainternals: [] }) as Box<Iterator<Item = usize>>,
    Requirements::B => Box::new(MatchingBs { binternals: [] }) as Box<Iterator<Item = usize>>,
    Requirements::C => Box::new(MatchingCs { cinternals: [] }) as Box<Iterator<Item = usize>>,
};

found.any(|m| m == 1)

There is, though, an allocation-less solution: use generics.

fn search<T: Iterator<Item = usize>>(t: T) -> bool {
    t.any(|m| m == 1)
}

and then apply that function to each branch of the match:

match requirements {
    Requirements::A => search(MatchingAs {ainternals: []}),
    Requirements::B => search(MatchingBs {binternals: []}),
    Requirements::C => search(MatchingCs {cinternals: []}),
}

The trade-off is that it's a bit closer to callback-hell, with a somewhat indirect flow.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722