Problem with specialization and generators


#1

Hi all -

I’m playing around with specialization and generators - but I suspect this problem is purely my missing something about specialization.

I’m getting the error

22 |             GeneratorState::Yielded(v) => Ok(Async::Ready(Some(v))),
   |                                                                ^ expected futures::Stream::Item, found std::ops::Generator::Yield
   |
   = note: expected type `<Gen<T> as futures::Stream>::Item`
              found type `<T as std::ops::Generator>::Yield`

from:

struct Gen<T>(T);

default impl<T> Stream for Gen<T> where T: Generator<Return = ()>,
{
    type Item = T::Yield;
    type Error = !;

    fn poll(&mut self) -> Result<Async<Option<Self::Item>>, Self::Error> {
        match self.0.resume() {
            GeneratorState::Yielded(v) => Ok(Async::Ready(Some(v))),

(playground)

My understanding is that Item = T::Yield should mean that Self::Item (ie, futures::Stream::Item) is the same as <T as Generator>::Yield.

What am I missing here? I’m guessing there’s some mixing between the general and specialized implementations, but don’t understand what.

I tried putting default in different places, but got much the same result.

Full code https://play.rust-lang.org/?gist=d7fe78dd4873da4488539573467ddf92&version=nightly

Thanks!


#2

Use PhantomData added to Gen to get it past the compiler error.


#3

I don’t understand what you’re suggesting. Could you clarify? What is the phantom data in this case?


#4

Here’s the relevant portion of the RFC: https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md#reuse

Here’s what’s going on:

You’ve provided a default impl of Stream for Gen<T>. I’ve boiled down the problem to the following.

struct Gen<T>(T);

default impl<T> Stream for Gen<T>
where
    T: Generator<Return = ()>,
{
    type Item = T::Yield;
    type Error = !;

    fn poll(&mut self) -> Result<Async<Option<Self::Item>>, Self::Error> {
        match self.0.resume() {
            GeneratorState::Yielded(v) => Ok(Async::Ready(Some(v))),
            GeneratorState::Complete(()) => Ok(Async::Ready(None)),
        }
    }
}

Removing the default keyword from the minimal example above fixes the error, which says that Self::Item is not equal to T::Yield - which is strange, since that’s apparently exactly what you’ve defined it to be. What you’re missing is that the default keyword means that more specific implementations may override any or all of the trait items. In other words, a more specific implementation may choose to override only Self::Item to be something not compatible with Generator::Yield, in which case your poll implementation no longer makes any sense (hence the compiler error).

So basically your default poll implementation depends on Self::Item not being overridden. Here’s a minimal example of your problem:
https://play.rust-lang.org/?gist=45897e38df20db5288c539ca019e604f&version=nightly

I’m not sure how you proceed from here. You could mark only poll to be default, but that would remove your ability to override Self::Item, which you appear to be doing. Hmmm…


#5

Turned out to fail just the same. After trying further and removing what I assumed were unrelated errors. (was adding S and setting both Yield=S and Item=S.)


#6

What you’re missing is that the default keyword means that more specific implementations may override any or all of the trait items. In other words, a more specific implementation may choose to override only Self::Item to be something not compatible with Generator::Yield, in which case your poll implementation no longer makes any sense (hence the compiler error).

OK, I was wondering it was something like that. I was hoping that

default impl ... {
    type ...
    fn ...
}

was different from

impl ... {
    default type ...
    default fn ...
}

in that the former meant that all the items would be specialized together, whereas the latter would mean they can all be specialized individually - but in fact they seem to be identical. (I have no special reason for thinking that there was a difference, other than it made intuitive sense to me.)


#7

Honestly - I’m not sure why it’s that way. I’m not sure exactly what the use cases are but I think it’d be useful to be able to specialize associated types - but being able to do so apparently removes your ability to use the associated type in any default impl that returns it (since that type could be anything, really).

Having the ability to specify a full default impl that includes associated types and also functions that return those associated types seems impossible. Hopefully someone with more experience w/ specialization can chime in here…


#8

What’s interesting is the RFC you linked above has an Example trait that has a blanket impl with a default associated type and function, and that apparently is meant to work (I’ve not tried it). That’s pretty close to your minimal example above that doesn’t work. So unless there’s some subtle difference, it’s not clear why it doesn’t work.


#9

Also, there’s a thread on rust internals asking for feedback on generators (amongst other things): https://internals.rust-lang.org/t/help-test-async-await-generators-coroutines/5835. It’s possible that posting this problem there will get more eyes from the core language team.


#10

Good catch – I’ve adapted the code from the Example trait and put it on the playground here: https://play.rust-lang.org/?gist=206eeaa6abaa8791ec252e8af0f9956a&version=nightly

It exhibits the same problem…and AFAIK you can’t use specialization without the default keyword so I don’t think I’m doing anything wrong.

I’ll post on that thread and link back to here…thanks again for popping into all of these threads @vitalyd

EDIT: scratch that, I put it on the specialization tracking issue (here)


#11

Thanks for posting on the github issue. I see we’re not alone in scratching our heads about specialization in general.

I adapted your playground link to be exactly the RFC example (just in case defaulting just the associated type and function somehow makes a difference) and it doesn’t compile. So either there’s a bug, the RFC is wrong, or that part of the RFC was not implemented (temporarily or permanently).

My own opinion is that these cases not working make specialization very limited. I understand that another impl may decide to specialize just the assoc type or just the fn (or both), but then I think there needs to be a way to indicate that multiple things need to be specialized as a unit so you don’t have this mix-n-match situation. Admittedly I’ve not played with specialization much so there may be good reasons for why things the way they are. But it sure does seem lacking.