Struggling with lifetimes & returning iterator from closure

The problem is that here:

fn choose_randomly<It>(
    game: &mut GameState,
    generator: impl FnOnce(&GameState) -> It,
) -> Option<It::Item>
where
    It: Iterator,

It must be a single type, and generator must produce that type for any input lifetime on &'lifetime GameState. If It captures the lifetime, it's a different type for every lifetime, and thus cannot meet this bound.

This is a case where the sugar of Fn traits becomes salt, because you can't not name the output type when using them. You're going to have to introduce your own redirection to be able to have the result vary in lifetime but still be arbitrarily short.

Here's one way. I rushed it, so maybe it could be a little cleaner. I also put a 'static bound on the eventual item, as otherwise you can't borrow &mut game for both the iterator and the RNG at the same time.

The hrtb is because Rust is bad at inferring higher-lifetime bounds of closures when you need them, so it needs a little assistance in this case.


Edit: Cleaned up a bit.

Other details I rushed over:

  • Type parameters like It must monomorphize to a single type.
  • &'lifetime Thing is a single type, but there is no single &Thing type that covers references of any lifetimes (i.e. there is no higher-ranked reference type). When you see &Thing in type position, it's either part of a higher-ranked type that operates over references of different lifetimes, or it's a &'lifetime Thing where 'lifetime is singular and inferred.
  • impl FnOnce(&GameState) -> It is short for impl for<'any> FnOnce(&'any GameState) -> It; this one is a higher-ranked type as a whole. But It here must be a singular type (i.e. the same for all lifetimes of 'any).
  • If you know the output type except for some lifetime parameter and single-type generics -- like if the output is always a Box<dyn Iterator<Item = Item> + '_> -- then you can use lifetime elision on stable.
  • Similarly, if you could use -> (impl Iterator<Item = Item> + '_), that would also work, but you can't yet
4 Likes