Impl Return Doesn't Work On Scoped Function

Hi all,

I've been experimenting with rust iterator, and stucked with this problem. I want to have a struct that holds an iterator to some type (in this case, let's say it's a string). But it's actually not just an iterator, but iterator with some filter and rev applied to it. I put the code below.
But for some reason, it doesn't work if I put it inside an impl block. But it works fine with free function that returns SomeStruct<impl Iterator<...>>.

I understand why it works on free function. But, not quite sure why it doesn't work on impl-scoped function. Do I need to explicitly write the concrete type like Iterator<Rev<Filter<...>>>? Is there a better way to do this?

use std::iter::Peekable;

struct SomeStruct<T>
where
    T: Iterator<Item = String>,
{
    data: Peekable<T>,
}

fn new(s: Vec<String>) -> SomeStruct<impl Iterator<Item=String>> {
    SomeStruct{
        data: s.into_iter().rev().filter(|s| s != "hahaha").peekable(),
    }
}

impl<T> SomeStruct<T>
where
    T: Iterator<Item = String>,
{
    fn new(s: Vec<String>) -> Self {
        Self {
            data: s.into_iter().rev().filter(|s| s != "hahaha").peekable(),
        }
    }
}

fn main() {}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/main.rs:22:19
   |
16 | impl<T> SomeStruct<T>
   |      - this type parameter
...
22 |             data: s.into_iter().rev().filter(|s| s != "hahaha").peekable(),
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `T`, found struct `Filter`
   |
   = note: expected struct `Peekable<T>`
              found struct `Peekable<Filter<Rev<std::vec::IntoIter<String>>, [closure@src/main.rs:22:46: 22:49]>>`

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

The type signatures are different:

fn new(s: Vec<String>) -> SomeStruct<impl Iterator<Item=String>>

means that the implementer of new chooses the Iterator type. On the other hand, the

impl<T> SomeStruct<T>
where
    T: Iterator<Item = String>,
{
    fn new(s: Vec<String>) -> Self
}

method is equivalent to a standalone function of signature

fn new<T: Iterator<Item = String>>(s: Vec<String>) -> SomeStruct<T>

which means that the caller of new chooses the Iterator type.


To get the equivalent as your free-standing function, you could write well… the following would compile

impl<T> SomeStruct<T>
where
    T: Iterator<Item = String>,
{
    fn new(s: Vec<String>) -> SomeStruct<impl Iterator<Item=String>> {
        SomeStruct {
            data: s.into_iter().rev().filter(|s| s != "hahaha").peekable(),
        }
    }
}

but that’s more the equivalent of

fn new<T: Iterator<Item = String>>(s: Vec<String>) -> SomeStruct<impl Iterator<Item=String>>

which leaves an additional unused T type argument that will cause problems calling the function; so chosing a concrete self type, you could chose the (somewhat ugly but workable) approach of chosing a concrete type unrelated to the iterator, as in

impl SomeStruct<std::iter::Empty<String>> {
    fn new(s: Vec<String>) -> SomeStruct<impl Iterator<Item = String>> {
        SomeStruct {
            data: s.into_iter().rev().filter(|s| s != "hahaha").peekable(),
        }
    }
}

fn main() {
    SomeStruct::new(vec![]); // works!
}

…while it would be nicer to get the Self type to match the SomeStuct<impl Iterator<…>> in the return type, this isn’t really possible without typing out the type – which in turn is impossible due to the closure type involved – or using unstable features such as type_alias_impl_trait.

#![feature(type_alias_impl_trait)]

type SomeStructNewIter = impl Iterator<Item = String>;

impl SomeStruct<SomeStructNewIter> {
    fn new(s: Vec<String>) -> SomeStruct<SomeStructNewIter> { // or `-> Self`
        SomeStruct {
            data: s.into_iter().rev().filter(|s| s != "hahaha").peekable(),
        }
    }
}

fn main() {
    SomeStruct::new(vec![]); // works!
}
2 Likes

Ah, that makes sense.

Interesting. I think this is what I'm looking for, but it seems the feature is not ready yet. Thanks man, I think using the std::iter::Empty to ignore the type parameter works ok for me.

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.