1

First, I wanted a function which would take generic collections but wouldn't consume it. That is, we have a function non_consuming.

fn non_consuming<'a,I>(_: &'a I)
where &'a I: std::iter::IntoIterator<Item = usize> {}

For specific reasons I'm not able to change the Item requirement to &usize. Such the compiler throws an error if I wanted to use this method normally like so:

non_consuming(&vec![0usize,1,2]);

Because IntoIterator is implemented for &Vec, but with Item = &T. Such I had the idea of implementing something like copied but for IntoIterator instead of Iterator.

My goal was to create a method into_copied for any IntoIterator with Item: &T which returns a struct C implementing IntoIterator with Item: T and &C should also implement IntoIterator with Item: T.

I use an extension trait to implement into_copied for any IntoIterator and the rest of the code is strongly oriented on how copied was implemented:

pub struct IntoIterCopied<I>
{
    into_iter: I,
}

impl<I> IntoIterCopied<I>
{
    pub fn new(iter: I) -> Self {
        IntoIterCopied{
            into_iter: iter,
        }
    }
}

impl<'a,I,T> IntoIterator for IntoIterCopied<I>
where
    I: IntoIterator<Item = &'a T>,
    T: 'a + Copy
{
    type Item = T;
    type IntoIter = std::iter::Copied<I::IntoIter>;
    fn into_iter(self) -> Self::IntoIter {
        self.into_iter.into_iter().copied()
    }
}

pub trait IntoIterCopiedExt {
    fn into_copied<'a, T>(self) -> IntoIterCopied<Self>
    where
        Self: Sized + IntoIterator<Item = &'a T>,
        T: 'a + Copy
    {
        IntoIterCopied::new(self)
    }
}

impl<T> IntoIterCopiedExt for T
where T: IntoIterator {}

I thought I could now use the function non_consuming like so:

let into_iter_adap = (&vec![0usize,1,2]).into_copied();
non_consuming(&into_iter_adap);

However this is the error I got:

error[E0277]: `&IntoIterCopied<&Vec<usize>>` is not an iterator
  --> src/main.rs:46:19
   |
40 | fn non_consuming<'a,I>(_: &'a I)
   |    ------------- required by a bound in this
41 | where &'a I: std::iter::IntoIterator<Item = usize>
   |              ------------------------------------- required by this bound in `non_consuming`
...
46 |     non_consuming(&into_iter_adap);
   |                   ^^^^^^^^^^^^^^^ `&IntoIterCopied<&Vec<usize>>` is not an iterator
   |
   = help: the trait `Iterator` is not implemented for `&IntoIterCopied<&Vec<usize>>`
   = note: required because of the requirements on the impl of `IntoIterator` for `&IntoIterCopied<&Vec<usize>>`

I'm not really sure what the error is telling me. I implemented IntoIterator for IntoIterCopied but not for &IntoIterCopied, so why is the note telling me it would be implemented?

Rust Playground

Nico227
  • 326
  • 2
  • 9
  • 1
    Your `where` clause doesn't enforce that `I` is an `IntoIterator` but rather that `&I` is an `IntoIterator`. Just because `IntoIterCopied` is an `IntoIterator` doesn't mean that `&IntoIterCopied` is. – kmdreko Feb 18 '21 at 11:55

2 Answers2

1

There's an amount of noise here that distracts from what I imagine the goal to be. Let's take one thing at a time.

Relax the signature of non_consuming

non_consuming has an unnecessarily limiting function signature: it demands that its argument be in the form &I, when all it actually requires is that the argument be IntoIterator<Item = usize>. This means that even if you had a Vec<usize> to pass (which you don't, but bear with me), you couldn't pass it to non_consuming; you'd be forced to keep it around in the caller. Deciding whether to consume or not, when it's not necessary, should be the caller's problem; this function shouldn't care. We can rewrite it without loss of generality:

fn maybe_consuming<II>(_: II)
where
    II: std::iter::IntoIterator<Item = usize>,
{
}

maybe_consuming will still accept any reference that non_consuming would, because II can be a reference.

Replace IntoIterCopied

You don't need to wrap an IntoIterator in a struct to give it an into_copied method. (If you wanted &a_vec to always yield usizes, you might consider wrapping a_vec, but it doesn't seem necessary in this case.) You can just make an extension trait that provides into_copied and returns a Copied<Self::IntoIter> directly.

pub trait IntoCopied {
    fn into_copied<'a, T>(self) -> std::iter::Copied<Self::IntoIter>
    where
        Self: Sized + IntoIterator<Item = &'a T>,
        T: 'a + Copy,
    {
        self.into_iter().copied()
    }
}

impl<I> IntoCopied for I where I: IntoIterator {}

Because Copied<I> is an Iterator and therefore implements IntoIterator, any code that worked with the original version should still work with this one.

Fix the caller

Since maybe_consuming has been changed to allow non-references, we can just pass .into_copied() directly to it:

fn main() {
    let vec_ref = &vec![0usize, 1, 2];
    maybe_consuming(vec_ref.into_copied());
}
trent
  • 25,033
  • 7
  • 51
  • 90
  • `non_consuming` has this signature because in my project it will delegate the collections to other functions. Such it has to be able to know that the reference of the collection is iterable (or that the iterable is `copy`). [Here is the Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c6a2fe286d41444852e15a5cd95c421d) to show what I'm trying to say. The remaining improvements can't be done without the relaxation of the signature (I think). – Nico227 Feb 18 '21 at 16:15
  • I added a paragraph specifying what I wanted to achieve with `into_copied`. I'm sorry for the inconvenience, should have been there from the start. – Nico227 Feb 18 '21 at 16:36
0

Thanks to the comment of @kmdreko, I got the code to compile. I only had to implement IntoIterator for &IntoIterCopied, so I added

impl<'a,I,T> IntoIterator for &'a IntoIterCopied<I>
where
    I: IntoIterator<Item = &'a T> + Copy,
    T: 'a + Copy
{
    type Item = T;
    type IntoIter = std::iter::Copied<I::IntoIter>;
    fn into_iter(self) -> Self::IntoIter {
        self.into_iter.into_iter().copied()
    }
}

to the code and it just worked fine.

Nico227
  • 326
  • 2
  • 9