crhino
crhino

Reputation: 559

Writing a generic trait implementation with IntoIterator trait, but only for immutable reference implementors

I am defining a trait that takes in a i: &I parameter. I would like to use this i value in a for loop.

For example:

struct Test;

trait Bar<I> {
    fn bar(&self, i: &I);
}

impl<T, I: IntoIterator<Item=T>> Bar<I> for Test {
    fn bar(&self, i: &I) {
        for x in i {
            println!("woo!");
        }
    }
}

fn main() {
    let vec = vec!(1, 2, 3);
    let test = Test;
    test.bar(&vec);
}

Playground link

This results in the error:

<anon>:10:9: 12:10 error: the trait `core::iter::Iterator` is not implemented for the type `&I` [E0277] <anon>:10         for x in i { <anon>:11             println!("woo!"); <anon>:12         } <anon>:10:9: 12:10 help: see the detailed explanation for E0277 <anon>:10:9: 12:10 note: `&I` is not an iterator; maybe try calling `.iter()` or a similar method <anon>:10:9: 12:10 note: required by `core::iter::IntoIterator::into_iter` error: aborting due to previous error playpen: application terminated with error code 101

I was playing around with using the Deref trait to see if I could get something to work, but to no avail.

I would really like to keep the immutable reference in the function definition as this trait is attempting to be generic over many types, and defining the other implementations using Bar<&'a I> has lead to some other lifetime related issues that I have been having trouble with as well.

Upvotes: 5

Views: 1831

Answers (2)

Francis Gagn&#233;
Francis Gagn&#233;

Reputation: 65782

In Rust, by convention, methods whose name starts with into take their argument by value and transform it into another value, usually reusing some resources from the original value. The IntoIterator trait and its into_iter follow that convention.

Are you sure you need to use &I in your trait? Your codes works just fine with I. That's because there is an implementation of IntoIterator for &Vec<T>. That's why one can write for x in &v where v is a Vec.

struct Test;

trait Bar<I> {
    fn bar(&self, i: I);
}

impl<T, I: IntoIterator<Item=T>> Bar<I> for Test {
    fn bar(&self, i: I) {
        for x in i {
            println!("woo!");
        }
    }
}

fn main() {
    let vec = vec!(1, 2, 3);
    let test = Test;
    test.bar(&vec);
}

playground

Upvotes: 2

huon
huon

Reputation: 102096

Saying that I is an IntoIterator doesn't say anything about &I, e.g. x..y is an IntoIterator (because it is an Iterator and all of them are), but &(x..y) is not.

You specifically want to bound &I, which fortunately can be done via a where clause, e.g.

impl<I, T> Bar<I> for Test 
    where for<'a> &'a I: IntoIterator<Item = T>
{
    fn bar(&self, i: &I) {
        for x in i {
            println!("woo!");
        }
    }
}

The for<'a> just means "for any lifetime 'a", and so the where clause is saying that &I is always an IntoIterator (just writing where &I: IntoIterator isn't quite enough).

There's some choices to be made about the T parameter there, e.g.

  1. IntoIterator<Item = T>
  2. IntoIterator<Item = &'a T>
  3. remove the parameter entirely and just write IntoIterator

The best choice will depend on exactly what you're doing with it. For the specific example in the question, I would go with 3, since the Item type doesn't matter at all. Number 2 makes sense because almost all types that have &T implement IntoIterator will yield references (it also seems to avoids most of the bugs/general difficulties the compiler currently has about reasoning about universal quantification over lifetimes, which hit 1 and 3).

Upvotes: 13

Related Questions