How can I explicitly say what an associated type should be?

Viewed 118

I have a trait which is something like Iterator, and a wrapper around it:

pub struct Wrapped<I: Iterator>(I);

Many of the functions on wrapped iterators return new wrapped iterators using impl-trait.

impl <I: Iterator> Wrapped<I> {
  pub fn foo(self) -> Wrapped<impl Iterator<Item=Foo<I::Item>>> {...}
  pub fn bar(self) -> Wrapped<impl Iterator<Item=Bar<I::Item>>> {...}
}

so that after a while, it becomes easy for the user to lose track of what the iterator item is in the calling code (eg. for expressions like my_wrapped.foo().bar().bar().foo()).

I'd like to give the user a way to explicitly specify what type they expect the Item to be such that if it's not that type then there's a compile-time error:

let y = x.foo().bar().bar().foo().assert_item_type::<Foo<Bar<Bar<Foo<X>>>>>()

but so far, the only way I've found to do this is a bit weird and ugly. Is there a cleaner way?

pub trait Is {
    type Myself;
}

impl<T> Is for T {
    type Myself = T;
}

impl <I: Iterator> Wrapped<I> {
    pub fn assert_item_type<Item: Is<Myself = I::Item>>(self) -> Self {
        self
    }
}

2 Answers

If you don't mind using a free-standing function, you can employ a similar approach without the need for any traits:

fn assert_item_type<I: Iterator<Item=T>, T>(x: Wrapped<I>) -> Wrapped<I> {
    x
}


let y = assert_item_type::<_, Foo<Bar<Bar<Foo<X>>>>>(x.foo().bar().bar().foo());

Unfortunately it is currently not possible to replace the I type parameter in assert_item_type with an impl Iterator (which would get rid of the underscore in the turbofish) as the compiler does not allow us to provide explicit generic arguments when impl Trait is used in argument position.

If and when this feature is introduced in the compiler, the function can then (hypothetically) be defined and used as:

fn assert_item_type<T>(x: Wrapped<impl Iterator<Item=T>>) -> Wrapped<impl Iterator<Item=T>> {
    x
}


let y = assert_item_type::<Foo<Bar<Bar<Foo<X>>>>>(x.foo().bar().bar().foo());

Playground

This seems like non-goal for an API. Instead, can't the user rely on typical impl trait syntax in their own code to accomplish this task?

e.g.

struct X;

fn accept_iterator(it: impl Iterator<Item = X>) { ... }

fn main() {
    let complex_iterator = x.foo().bar().foo().bar()...;
    // Will only compile if `complex_iterator` is an iterator of `X`s
    accept_iterator(complex_iterator);
}

While it isn't a dedicated method for this task, it accomplishes what you desire in a more idiomatic way.

Related