Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to express a Fn generic with a lifetime involved without falling into 'static #108852

Closed
nappa85 opened this issue Mar 7, 2023 · 2 comments

Comments

@nappa85
Copy link

nappa85 commented Mar 7, 2023

Long story short, I was trying to make a functional programming compatible version of Iterator::peekable (code can be found also in my repo)

pub trait LendingIterator {
    type Item<'a>
    where
        Self: 'a;
    fn next(&'_ mut self) -> Option<Self::Item<'_>>;
}

pub trait PeekableIterator: Iterator + Sized {
    fn peekable_iter(self) -> Peekable<Self> {
        Peekable::new(self)
    }
}

impl<T: Iterator> PeekableIterator for T {}

pub struct Peekable<T: Iterator> {
    inner: T,
    next: Option<T::Item>,
}

impl<T: Iterator> Peekable<T> {
    fn new(mut inner: T) -> Self {
        let next = inner.next();
        Peekable { inner, next }
    }
    fn next(&mut self) -> Option<T::Item> {
        std::mem::replace(&mut self.next, self.inner.next())
    }
}

impl<T: Iterator> LendingIterator for Peekable<T> {
    type Item<'a> = (T::Item, Option<&'a T::Item>) where T: 'a;
    fn next(&'_ mut self) -> Option<Self::Item<'_>> {
        self.next().map(|t| (t, self.next.as_ref()))
    }
}

pub trait LendingIteratorExt: LendingIterator {
    fn for_each<F>(self, mut f: F)
    where
        Self: Sized,
        F: FnMut(Self::Item<'_>),
    {
        #[inline]
        fn call<T>(mut f: impl FnMut(T)) -> impl FnMut((), T) {
            move |(), item| f(item)
        }

        self.fold((), call(f));
    }
    fn fold<B, F>(mut self, init: B, mut f: F) -> B
    where
        Self: Sized,
        F: FnMut(B, Self::Item<'_>) -> B,
    {
        let mut accum = init;
        while let Some(x) = self.next() {
            accum = f(accum, x);
        }
        accum
    }
}

impl<T: LendingIterator> LendingIteratorExt for T {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let a = [0, 1, 2, 3, 4];
        a.iter().peekable_iter().for_each(|(a, b)| {
            if let Some(b) = b {
                assert_eq!(*a, *b - 1)
            }
        });
    }
}

First problem you can see is on the for_each implementation, copied from std. Here the problem seems to be the two different impl Trait using two different lifetimes for Self::Item<'_>.
Here I've tried several approaches, for example using an HRTB

F: for<'a> FnMut(Self::Item<'a>), // in for_each
F: for<'a> FnMut(B, Self::Item<'a>) -> B, // in fold

Or adding a lifetime at method level, like

 fn for_each<'a, F>(self, mut f: F)
  where
      Self: Sized,
      F: FnMut(Self::Item<'a>) {}

  fn fold<'a, B, F>(mut self, init: B, mut f: F) -> B
  where
      Self: Sized,
      F: FnMut(B, Self::Item<'a>) -> B {}

Both approaches end up asking for a 'static lifetime, as can be seen trying to run the test

I've searched for similar issues, like that but haven't found a solution so far

@jyn514
Copy link
Member

jyn514 commented May 24, 2023

This doesn't seem to be a bug as such, just a problem you were trying to solve you ran into difficulty with.

The issue tracker is not a support forum. For future questions please ask in one of these places:

https://discord.gg/rust-lang/
https://discord.gg/rust-lang-community/
https://users.rust-lang.org/
https://rust-lang.zulipchat.com/

@jyn514 jyn514 closed this as not planned Won't fix, can't repro, duplicate, stale May 24, 2023
@nappa85
Copy link
Author

nappa85 commented May 24, 2023

Ok, thanks @jyn514 for the reply.

Just to leave something for the poor people who lands here searching for the same problem, this version works:

pub trait LendingIterator {
    type Item<'a>
    where
        Self: 'a;
    fn next(&'_ mut self) -> Option<Self::Item<'_>>;
}

pub trait PeekableIterator: Iterator + Sized {
    fn peekable_iter(self) -> Peekable<Self> {
        Peekable::new(self)
    }
}

impl<T: Iterator> PeekableIterator for T {}

pub struct Peekable<T: Iterator> {
    inner: T,
    next: Option<T::Item>,
}

impl<T: Iterator> Peekable<T> {
    fn new(mut inner: T) -> Self {
        let next = inner.next();
        Peekable { inner, next }
    }
    fn next(&mut self) -> Option<T::Item> {
        std::mem::replace(&mut self.next, self.inner.next())
    }
}

impl<T: Iterator> LendingIterator for Peekable<T> {
    type Item<'a> = (T::Item, Option<&'a T::Item>) where T: 'a;
    fn next(&'_ mut self) -> Option<Self::Item<'_>> {
        self.next().map(|t| (t, self.next.as_ref()))
    }
}

pub trait LendingIteratorExt: LendingIterator {
    fn for_each<F>(self, mut f: F)
    where
        Self: Sized,
        F: for<'a> FnMut(Self::Item<'a>),
    {
        /*
        #[inline]
        fn call<T>(mut f: impl FnMut(T)) -> impl FnMut((), T) {
            move |(), item| f(item)
        }

        self.fold((), call(f));
        */
        self.fold((), move |_, item| f(item))
    }
    fn fold<B, F>(mut self, init: B, mut f: F) -> B
    where
        Self: Sized,
        F: for<'a> FnMut(B, Self::Item<'a>) -> B,
    {
        let mut accum = init;
        while let Some(x) = self.next() {
            accum = f(accum, x);
        }
        accum
    }
}

impl<T: LendingIterator> LendingIteratorExt for T {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let a = [0, 1, 2, 3, 4];
        //a.iter().peekable_iter().for_each(|(a, b)| {
        a.into_iter().peekable_iter().for_each(|(a, b)| {
            if let Some(b) = b {
                //assert_eq!(*a, *b - 1)
                assert_eq!(a, *b - 1)
            }
        });
    }
}

Why does it works? Well, there are two main reasons:

  • I've discarded the optimization inside LendingIteratorExt::for_each, since the compiler doesn't manage lifetimes correctly between the two impl Trait (I think I saw an RFC about it recently, but can't find it right now)
  • inside the test I'm using into_iter instead of iter to remove the lifetime from the starting iterator, that seems to be the main problem here, I don't know if it's a borrow checker problem or a code bug, but every single person I've shown the code to haven't found a viable solution

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants