3

I have a piece of code as follows:

fn stream_it(&self) -> Box<dyn Stream<Item=()>> {
   Box::new(self.some_field)
}

fn consume_it(&self) {
   let a = self.stream_it().map(|i| i);
}

And I am getting the compilation error:

error: the `map` method cannot be invoked on a trait object
   --> ...
    |
69  |       let a = self.stream_it().map(|i| i);
    |                                ^^^
    | 
   ::: ...
    |
257 |         Self: Sized,
    |               ----- this has a `Sized` requirement
    |
    = note: other candidates were found in the following traits, perhaps add a `use` for one_of_them:
            candidate #1: `use futures_util::future::future::FutureExt;`
            candidate #2: `use futures_signals::signal::signal::SignalExt;`
            candidate #3: `use futures_util::stream::stream::StreamExt;`
            candidate #4: `use futures_signals::signal_vec::SignalVecExt;`
            candidate #5: `use async_std::stream::stream::StreamExt;`

I understand that the Sized requirement is necessary, but I don't know how to fulfill it. Is it even possible to map over a Stream of unit?

Peter Hall
  • 36,534
  • 10
  • 79
  • 144
OneEyeQuestion
  • 663
  • 6
  • 17

1 Answers1

5

If you look at the definition of StreamExt::map, which is automatically implemented by types that implement Stream, you'll see this:

fn map<T, F>(self, f: F) -> Map<Self, F>
where
    F: FnMut(Self::Item) -> T

In other words, map takes ownership of self. But Box<dyn Stream> doesn't implement Stream, so it will instead attempt to use the dyn Stream value contained within. But that is a trait object, which is an unsized type, and can never be a self parameter without a reference, hence your compiler error.

The solution here would be to change the return type into Pin<Box<dyn Stream<Item = ()>>>, which does implement Stream:

use std::pin::Pin;

fn stream_it() -> Pin<Box<dyn Stream<Item=()>>> {
//                ^-- add Pin<...> here
   Box::pin(futures::stream::iter(vec![(), (), ()]))
//      ^-- use Box::pin instead of Box::new
}

Alternatively, you can also change the return type to Box<dyn Stream<Item = ()> + Unpin>, which also does implement Stream, but puts some additional requirements on the stream that is returned (namely that it implements Unpin and is therefore safe to move around in memory), so it's generally less preferable.

Community
  • 1
  • 1
Frxstrem
  • 30,162
  • 8
  • 66
  • 99
  • And what do you mean with "But Box doesn't implement Stream, so it will instead attempt to use the dyn Stream value contained within"? How is it that it attempts to unpack the Box to see if what is inside implements stream? – OneEyeQuestion Apr 17 '20 at 23:08
  • 2
    This is possible due to so-called `Deref` coercions - https://doc.rust-lang.org/1.30.0/book/2018-edition/ch15-02-deref.html - and auto-dereferencing for method calls - https://stackoverflow.com/questions/28519997/what-are-rusts-exact-auto-dereferencing-rules - which, when combined, let us treat `Box` as `T`. – Cerberus Apr 18 '20 at 03:50
  • @Frxstrem Expanding further on this comment: "but puts some additional requirements on the stream that is returned (namely that it implements Unpin and is therefore safe to move around in memory)" Why is that less prefereable? Isn't only that a type isn't self-referential for it to be `Unpin`, something which isn't very common? – OneEyeQuestion Apr 20 '20 at 04:05