How does importing StreamExt magically inject methods?

This function won't work unless StreamExt is imported.
let (write, read) = ws_stream.split();

src\main.rs:31:35
     |
31   |     let (write, read) = ws_stream.split();
     |                                   ^^^^^ method not found in `WebSocketStream<Stream<TokioAdapter<TcpStream>, TokioAdapter<TlsStream<TcpStream>>>>`

use futures::StreamExt;

Now I am new to Rust, and I can't see how it is possible for something to appear in a different package or object by importing something in a higher scope.

Any explanations? Does it have to do with the async runtime I use for main?

Also why is StreamExt not already imported inside the websocket package?

The methods are defined in the trait, so the trait needs to be "in scope", meaning you have to bring it into scope with a use.

This section in the book has an example of this where "the user must bring the trait into scope as well as the types".

That I don't know. The answer is probably specific to the websocket package.

Oh, probably because there is more than one StreamExt.

Be aware that the Stream trait in Tokio is a re-export of the trait found in the futures crate, however both Tokio and futures provide separate StreamExt utility traits, and some utilities are only available on one of these traits. Click here to see the other StreamExt trait in the futures crate.

Maybe it is, maybe it isn't; in any case, it doesn't matter. Your module has to import the trait itself in order to use its methods. No other external crate can possibly help you with this. It is literally your own code that must lexically contain the use statements.

There is no magic "injection" going on. You are probably confusing the implementation of traits with the declaration of what traits you intend to use. The implementations were there all along. However, the compiler intentionally restricts the use of items from traits. That is done in order to force explicitness and disambiguate between identically named methods of different traits.

1 Like

My reply above assumes you wanted to know why StreamExt isn't re-exported by the websocket package using pub use, which would allow you to use it (import it) from the websocket package. The reply from @H2CO3 is a better explanation if that's not what you meant.

I'm looking inside the source code and there is no method called split.
What makes it appear there after importing StreamExt?


impl<S> WebSocketStream<S> {
    /// Convert a raw socket into a WebSocketStream without performing a
    /// handshake.
    pub async fn from_raw_socket(stream: S, role: Role, config: Option<WebSocketConfig>) -> Self
    where
        S: AsyncRead + AsyncWrite + Unpin,
    {
        handshake::without_handshake(stream, move |allow_std| {
            WebSocket::from_raw_socket(allow_std, role, config)
        })
        .await
    }

    /// Convert a raw socket into a WebSocketStream without performing a
    /// handshake.
    pub async fn from_partially_read(
        stream: S,
        part: Vec<u8>,
        role: Role,
        config: Option<WebSocketConfig>,
    ) -> Self
    where
        S: AsyncRead + AsyncWrite + Unpin,
    {
        handshake::without_handshake(stream, move |allow_std| {
            WebSocket::from_partially_read(allow_std, part, role, config)
        })
        .await
    }

    pub(crate) fn new(ws: WebSocket<AllowStd<S>>) -> Self {
        Self {
            inner: ws,
            closing: false,
            ended: false,
            ready: true,
        }
    }

    fn with_context<F, R>(&mut self, ctx: Option<(ContextWaker, &mut Context<'_>)>, f: F) -> R
    where
        S: Unpin,
        F: FnOnce(&mut WebSocket<AllowStd<S>>) -> R,
        AllowStd<S>: Read + Write,
    {
        #[cfg(feature = "verbose-logging")]
        trace!("{}:{} WebSocketStream.with_context", file!(), line!());
        if let Some((kind, ctx)) = ctx {
            self.inner.get_mut().set_waker(kind, ctx.waker());
        }
        f(&mut self.inner)
    }

    /// Returns a shared reference to the inner stream.
    pub fn get_ref(&self) -> &S
    where
        S: AsyncRead + AsyncWrite + Unpin,
    {
        self.inner.get_ref().get_ref()
    }

    /// Returns a mutable reference to the inner stream.
    pub fn get_mut(&mut self) -> &mut S
    where
        S: AsyncRead + AsyncWrite + Unpin,
    {
        self.inner.get_mut().get_mut()
    }

    /// Returns a reference to the configuration of the tungstenite stream.
    pub fn get_config(&self) -> &WebSocketConfig {
        self.inner.get_config()
    }

    /// Close the underlying web socket
    pub async fn close(&mut self, msg: Option<CloseFrame<'_>>) -> Result<(), WsError>
    where
        S: AsyncRead + AsyncWrite + Unpin,
    {
        let msg = msg.map(|msg| msg.into_owned());
        self.send(Message::Close(msg)).await
    }
}

The split method is defined on the StreamExt trait. Since WebSocketStream<S> implements StreamExt, you can call StreamExt methods on WebSocketStream<S>. But only when StreamExt is imported.

You can't find split in the websocket source code because it has a default implementation where StreamExt is defined. You can find it here: link

The WebSocketStream<S> type implements StreamExt because of this line of code:

impl<T> Stream for WebSocketStream<T>
where
    T: AsyncRead + AsyncWrite + Unpin,
{ ... }

link

and this:

impl<T: ?Sized> StreamExt for T where T: Stream {}

link

The first snippet says "WebSocketStream<T> implements Stream whenever T implements the AsyncRead, AsyncWrite, and Unpin traits.

The second snippet says "anything that implements Stream also implements StreamExt". This is called a blanket implementation.

Thus, since WebSocketStream<T> implements Stream, it must also implement StreamExt.


To get back to the original question: To call a trait method, the trait must be imported. This is always the case for all traits and all trait methods. Under all circumstances. Including this one.

Why?

Because traits can be implemented for types outside of your crate. For example:

trait StringExt {
    fn twice_len(&self) -> usize;
}

impl StringExt for String {
    fn twice_len(&self) -> usize {
        self.len() * 2
    }
}
impl StringExt for &str {
    fn twice_len(&self) -> usize {
        self.len() * 2
    }
}

fn main() {
    println!("{}", "foo".twice_len());
}

This adds a new method to String. Imagine if you import a crate containing the above. Should this magically add a new method to string types in the rest of the project?

No, it does not. To use twice_len, you must import StringExt. That avoids weird situations where adding a new dependency could add new methods without otherwise changing the source code.

6 Likes

All these answers are so elaborate and thorough. This community is amazing. I don't know how you have the time to answer all these questions in this forum, but thank you all. Trying to do small steps in rust and really asking about things that are totally new to me.

3 Likes

the small example really made me understand.

There absolutely is.