Missing a type parameter or trait bound

Trying to hold an Iterator generated from IntoIterator. Need some help about the compile error.

struct Calories<I>(I);

impl<I: Iterator<Item = String>> Calories<I> {
    fn new<S>(source: S) -> Self
    where
        S: IntoIterator<Item = String>,
        S::IntoIter: Iterator<Item = String>,
    {
        Self(source.into_iter())
    }
}

fn main() {}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/main.rs:9:14
  |
3 | impl<I: Iterator<Item = String>> Calories<I> {
  |      - this type parameter
...
9 |         Self(source.into_iter())
  |         ---- ^^^^^^^^^^^^^^^^^^ expected type parameter `I`, found associated type
  |         |
  |         arguments to this function are incorrect
  |
  = note: expected type parameter `I`
            found associated type `<S as IntoIterator>::IntoIter`
  = note: you might be missing a type parameter or trait bound
note: tuple struct defined here
 --> src/main.rs:1:8
  |
1 | struct Calories<I>(I);
  |        ^^^^^^^^

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error

Rust Playground

2 Likes

Simple and straightforward solution. Thanks!

I found I enter to same error while attempting to implement FromIterator<String> for Calories<I>.
Compiler is not happy about S: IntoIterator<IntoIter = I>.
playground

FromIterator<Item> is designed for things which consume the iterator altogether, and thus don't care what the IntoIter type is. They have to have one implementation for all T: IntoIterator<Item = Item> -- that's why the bound is on the trait method. It looks like you tried to work around this by changing the bounds on the trait method, but that's not allowed -- the point of traits is that implementors fulfill the prescribed ability.

Or to put it more concretely, there's no way for this to work with generics because if it did, someone could call

type VecInIt = std::vec::IntoIter<String>;
type VecDeqInIt = std::collections::vec_deque::IntoIter<String>;

/// ...

let cal: Calories<VecInIt> =
    <Calories<VecInIt> as FromIterator<String>>
        ::from_iter::<VecDeqInIt>(iter);

But this makes no sense; you can't get a VecInIt out of a VecDeqInIt.


There is no way to "smuggle" the IntoIterator::IntoIter that's generic at the function level into the returned Self type, which is always the same type within the impl block (even if generic at the impl header level). Type erasure (Box<dyn Iterator<Item=String>>) doesn't help as the trait method has to accept even lifetime-carrying IntoIterator types, and there's no sound way to erase the lifetimes of those so that you can get the single Self type.


In summary, this trait doesn't do the task you're trying to accomplish, there's no sound way to force it to, and you need a different tool.

2 Likes

You can implement From<S> where S: IntoIterator<Item = String> though:

impl<S> From<S> for Calories<<S as IntoIterator>::IntoIter>
where
    S: IntoIterator<Item = String>,
{
    fn from(source: S) -> Self {
        Self::new(source)
    }
}

(Playground)


P.S.: Not sure if such blanket implementations are a good idea. I think I sometimes got into trouble with potentially overlapping implementations; i.e. you can't do this for several traits but only for one (Playground).

2 Likes

Thanks! Generic trait method indicate that one implementor type has to deal with all kind of concrete method is a very good point. Simply making the implementor generic won't fullfill such contract.

I try to chain Calories as a part of iterator dot syntax. Something like

    v.into_iter()
     .map(...)
     .collect::<Calories<_>>()
     .filter(...);

Seems the implementation would turn unnecessarily complicated.
I'll go break the chain to keep the code simple.
Thanks for your answer.

.collect is a method to terminate a chain of iterator method calls, and create a collection (or single value) from the result of executing / iterating through the iterator. If your Calories<T> type is a new iterator itself, then working with .collect is the wrong approach. If you want a nice-looking method in an iterator chain, then define your own extension trait for Iterators, similar to how the itertools crate does it.

Rust Playground

3 Likes

The IteratorExt is a good pattern for implementing chain method. I also found that I can chain the constructor of Calories<T> by implemting an Apply trait. Thanks for your help again!
Rust Playground

Couldn't this Apply trait be defined very generically?

-trait Apply<Output>: Sized {
-  fn apply(self, f: fn(Self) -> Output) -> Output {
+trait Apply: Sized {
+  fn apply<T>(self, f: fn(Self) -> T) -> T {
     f(self)
   }
 }
 
-impl<I: Iterator<Item = String>> Apply<Calories<I>> for I {}
+impl<T> Apply for T {}
 
 fn main() {
   let vs = ["100", "200", "300"];
   let max = vs.into_iter().map(String::from).apply(Calories::new).fold(0usize, usize::max);
   println!("{}", max);
 }

(Playground)

I wonder, does such a generic apply method exist somewhere already?


Or even more generic:

 trait Apply: Sized {
-  fn apply<T>(self, f: fn(Self) -> T) -> T {
+  fn apply<T, F: FnOnce(Self) -> T>(self, f: F) -> T  {
     f(self)
   }
 }

(Playground)

E.g. tap::Pipe::pipe

1 Like

Interesting! A difference is that tap::Pipe is implemented for all T: ?Sized which makes sense because some of the additional methods (e.g. tap::Pipe::pipe_ref) work on references, thus it's possible to support types which are !Sized (like [u8]).

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.