Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Traits returning a trait: In some cases works, in others it doesn't

Tags:

rust

I need to implement a trait that is returning the futures::StreamExt trait.

In general this sounds easy and there are several answers to this e.g. this here.

I tried this with StreamExt but this does - for some reason - not work. Here my sample code:


// [dependencies]
// futures = "0.3.6"
// async-std = { version = "1.6.5", features = ["attributes", "unstable"] } // interval function is unstable atm.
use std::time::Duration;

use futures::StreamExt;

trait StreamProvidingTrait {
  fn returnastream(self: &Self) -> Box<dyn StreamExt<Item=i32>>;
}

struct StreamProvider {}

impl StreamProvidingTrait for StreamProvider {
  fn returnastream(self: &Self) -> Box<dyn StreamExt<Item=i32>> {
    return Box::new(async_std::stream::interval(Duration::from_millis(1000)).map(|_| 1));
  }
}

#[async_std::main]
async fn main() {
  let mut counter = 0;
  let object = StreamProvider {};

  // creates a stream
  let worx = object.returnastream();
  // mappes the stream into something....
  let mut mapped_stream = worx.map(|_| {
    counter = counter + 1;
    counter
  });
  // subscribing to the items
  while let item = mapped_stream.next().await {
    match item {
      Some(value) => println!("{}", value),
      _ => {}
    }
  }
}

And here the error:

Compiling traittest v0.1.0 (/Users/andre/repos/traittest)
warning: the trait `futures_util::stream::stream::StreamExt` cannot be made into an object
--> /Users/andre/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.6/src/stream/stream/mod.rs:1326:8
|
1326 |     fn poll_next_unpin(&mut self, cx: &mut Context<'_>) -> Poll<Option<Self::Item>>
|        ^^^^^^^^^^^^^^^ the trait cannot be made into an object because method `poll_next_unpin` references the `Self` type in its `where` clause
|
= note: `#[warn(where_clauses_object_safety)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #51443 <https://github.com/rust-lang/rust/issues/51443>

error[E0038]: the trait `futures_util::stream::stream::StreamExt` cannot be made into an object
--> src/main.rs:6:36
|
6    |   fn returnastream(self: &Self) -> Box<dyn StreamExt<Item=i32>>;
|                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `futures_util::stream::stream::StreamExt` cannot be made into an object
|
::: /Users/andre/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.6/src/stream/stream/mod.rs:226:8
|
226  |     fn next(&mut self) -> Next<'_, Self>
|        ---- the trait cannot be made into an object because method `next` references the `Self` type in its return type
...
976  |     fn by_ref(&mut self) -> &mut Self {
  |        ------ the trait cannot be made into an object because method `by_ref` references the `Self` type in its return type
  ...
      1381 |     fn select_next_some(&mut self) -> SelectNextSome<'_, Self>
  |        ---------------- the trait cannot be made into an object because method `select_next_some` references the `Self` type in its return type

  error[E0038]: the trait `futures_util::stream::stream::StreamExt` cannot be made into an object
      --> src/main.rs:12:36
      |
      12   |   fn returnastream(self: &Self) -> Box<dyn StreamExt<Item=i32>> {
    |                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `futures_util::stream::stream::StreamExt` cannot be made into an object
        |
    ::: /Users/andre/.cargo/registry/src/github.com-1ecc6299db9ec823/futures-util-0.3.6/src/stream/stream/mod.rs:226:8
        |
        226  |     fn next(&mut self) -> Next<'_, Self>
    |        ---- the trait cannot be made into an object because method `next` references the `Self` type in its return type
    ...
        976  |     fn by_ref(&mut self) -> &mut Self {
      |        ------ the trait cannot be made into an object because method `by_ref` references the `Self` type in its return type
      ...
          1381 |     fn select_next_some(&mut self) -> SelectNextSome<'_, Self>
      |        ---------------- the trait cannot be made into an object because method `select_next_some` references the `Self` type in its return type

      error: aborting due to 2 previous errors; 1 warning emitted

      For more information about this error, try `rustc --explain E0038`.
      error: could not compile `traittest`.

      To learn more, run the command again with --verbose.

          Process finished with exit code 101

When I exchange the StreamExt trait with my own one, this code works perfectly.


trait SomeTrait {
  fn returnatrait(self: &Self) -> Box<dyn SomeOtherTrait>;
}

trait SomeOtherTrait {
  fn sayHelloWorld(&self);
}

struct DummyStruct {}

impl SomeOtherTrait for DummyStruct {
  fn sayHelloWorld(&self) {
    println!("hello world");
  }
}

struct Implementation {}

impl SomeTrait for Implementation {
  fn returnatrait(self: &Self) -> Box<dyn SomeOtherTrait> {
    return Box::new(DummyStruct{});
  }
}

fn main() {
  let implementation = Implementation{};

  let worx = implementation.returnatrait();

  worx.sayHelloWorld();
}

What's wrong here? There's obviously something I don't understand. Please help me understand this!

like image 428
andre Avatar asked Oct 12 '20 20:10

andre


Video Answer


1 Answers

A function returning a trait can use the impl Trait syntax to return an opaque type that implements the trait. A trait method returning a trait currently doesn't support this feature and requires the trait to be returned as a trait object - a dynamically dispatched reference or smart pointer such as Box or Rc. Not all traits are object-safe, though, and the bad news is that StreamExt is among those that aren't, for reasons pointed out by the compiler, such as referencing Self in method return types and where clauses.

The good news, however, is that this is not a problem: StreamExt is an extension trait, one that provides a blanket implementation for all types that implement Stream. So you don't need to bother returning a dyn StreamExt trait object, you can return a dyn Stream one, and you'll still get access to StreamExt methods simply by requesting them with use StreamExt. In other words, you can just replace Box<dyn StreamExt> with Box<dyn Stream> in the return type of your trait.

Another issue you might encounter is that Box<dyn Stream> doesn't work on methods that need to move the stream, which includes many methods provided by StreamExt. Those will require the stream to be pinned, which can be fixed by returning Pin<Box<dyn Stream>> instead of Box<dyn Stream>. There is even a boxed() method on StreamExt that pins and boxes the stream in one operation; code that uses it would look like this (playground):

use futures::stream::{Stream, StreamExt};
use std::pin::Pin;

trait StreamProvidingTrait {
    fn returnastream(&self) -> Pin<Box<dyn Stream<Item = i32>>>;
}

struct StreamProvider {}

impl StreamProvidingTrait for StreamProvider {
    fn returnastream(&self) -> Pin<Box<dyn Stream<Item = i32>>> {
        return tokio::stream::once(0).boxed();
    }
}

fn main() {
    let provider = StreamProvider {};
    let stream = provider.returnastream();
    let _fut = stream.into_future();
}
like image 138
user4815162342 Avatar answered Nov 15 '22 09:11

user4815162342