A year in Rust. And I still can't read the docs!

I agree! There is probably some downside to this which I haven’t thought of since it’s so rare, but I’ve certainly spent quite a bit of time trying to solve a problem which is caused by me depending on the wrong version of a library.

Memories of depending on the wrong version of futures_state_stream and the errors that generated in long future chains (back when we used Futures 0.1) still haunts me...

There's some previous discussion here at least: https://github.com/rust-lang/api-guidelines/issues/176
This is something I've been meaning to create a thread on, as I'm not sure I fully understand the implications of the downsides mentioned in the issue. They don't seem that dramatic though.

Yay! Finally got a simple tokio-tunstenite web socket server working!

Maybe I'm daft and making more work for myself but my approach here was:

  1. Start with the tokio-tungstenite server example, which at least works after much futzing around.

  2. Remove all the dependencies I feel I should not need when using a high level async lib like tokio. Out goes: futures-channel, futures-util, tungstenite.

  3. Delete most of the server example code and start to build it up from scratch. After all it's all broken now and I don't want to see things like pin_mut, future::select or futures_channel::mpsc in there. I like to think these are low level implementation details I should not need to use with a lib like tokio. Also why does the example not use tokio channels?

For anyone curious, after another day of frustration I finally come up with this little server example:

#![warn(rust_2018_idioms)]
#[macro_use]
extern crate log;

use std::net::SocketAddr;
use tokio::net::{TcpListener, TcpStream};
use tokio::time::{delay_for, Duration};
//use tokio::stream::StreamExt;
use futures::{SinkExt, StreamExt};

async fn handle_connection(raw_stream: TcpStream, addr: SocketAddr) {
    info!("Incoming TCP connection from: {}", addr);

    let ws_stream = tokio_tungstenite::accept_async(raw_stream)
        .await
        .expect("Error during the websocket handshake occurred");
    info!("WebSocket connection established: {}", addr);

    // We have to split the web socket connection in order to get something to send and receive on.
    let (mut outgoing, mut incoming) = ws_stream.split();

    loop {
        let msg = incoming.next().await.unwrap().unwrap();
        info!(
            "Received a message from {}: {}",
            addr,
            msg.to_text().unwrap()
        );

        loop {
            // Send some junk messages back for a test
            let msg = tokio_tungstenite::tungstenite::Message::Text("Hello\n".to_string());

            info!("Sending msg: \"{}\".", msg);
            outgoing.send(msg).await.unwrap();

            delay_for(Duration::from_millis(1000)).await;
            println!("100 ms have elapsed");
        }
    }
}

#[tokio::main]
async fn main() {

    // Create the event loop and TCP listener we'll accept connections on.
    let try_socket = TcpListener::bind(&addr).await;
    let mut listener = try_socket.expect("Failed to bind");
    info!("Listening on: {}", addr);

    // Spawn the handling of each connection in a separate task.
    while let Ok((stream, addr)) = listener.accept().await {
        tokio::spawn(async move { handle_connection(stream, addr).await });
    }
}

Hurray! It works! And no more mysterious junk in there.

Well, except one thing...

You will notice that in the above I have:

//use tokio::stream::StreamExt;
use futures::{SinkExt, StreamExt};

That is the last pesky reference to any 'futures' in the code. It looks like I should be able to use tokio::stream::StreamExt. And why not this is a tokio system we are build here right?

Well if I swap those comments around I find I don't have a 'split()' method anymore.

Thanks for all the input everyone.

Regarding your splitting, the Sink trait doesn't exist in the eyes of Tokio, so Tokio's StreamExt can't split something that is half Sink. If tokio-tungstenite uses the Sink trait, you're going to need the SinkExt from futures to talk to it.

I can live with that.

I wonder if there is a reason particular reason for tokio-tungstentite not providing a .split() on a web socket stream it has given me?

Dropping down to using futures seems like monkeying with low level implementation details when using a high level library API like tokio.

I would help if they would document such dependencies. It has taken me two days or so to get a simple web socket server and client working with tokio-tunstenit. Grrr...

Thanks.

Tokio-tungstenite doesn't provide a split function because there's a split function in the futures crate that works on all Stream/Split types.

Ok. Make sense.

Any idea why they use futures_channel::mpsc in the example rather than tokio::sync::mpsc?

My guess would be that futures_channel crate is the first async channel they heard of, so they just keep using that one.

Historical baggage then. Understandable.

hysterical raisins :wink:

That link is hysterical. So funny I could cry. This whole thread is about the hours I spent on this, mostly caused by incompatibility, backwards, forwards, sideways, I'm not sure which.

:slight_smile:

One bit I've learned (also a Rust new user, though longtime coder in other languages): anytime I see Pin, async, or await, I take a deep breath and try to be patient. This is a fairly new part of the language that has undergone a lot of changes recently (last 9 months or so), and even the main docs on this (Asynchronous Programming in Rust) aren't complete yet. Many of the libraries are still catching up to this, if not in implementation then in documentation.

And even when the documentation is updated, alas, search engines still return older examples from time to time that lead me astray.

Very wise.

I'm comfortable with the idea of async and await. Luckily I managed to rewrite the tokio-tunstenite example such that is did not use Pin. Which sounds like some low level implementation detail I should not have to worry about.

Now that is a puzzle to me.

For example, I'm using the nats crate to talk to our NATS server which does not support async. And I'm using tokio-tungstenite as a web socket server, which is async.

Getting those to play together is a pain in the butt as you move from the sync to async world and back.

It is on the road map that nats will get async support at some time.

Is it really expected that every such crate support both sync and async? Two parallel universes in the same language?

futures isn’t really a “low-level” crate, especially the *Ext traits it provides are pretty much identical to what tokio and async-std provide (except you know for a fact they’re not tied to the runtime in any way). It does have some low level utilities as well, which I suppose you could say pin_mut! is when you could just Pin::box instead; but they’re very commonly useful utilities when wanting to avoid unnecessary runtime costs.

I think what ends up happening in practice is that the sync version calls the async version under the hood. I’ve seen that in a few crates now. It’s probably not the most ideal since there’s probably some overhead associated with that (and potentially a lot of you’re not careful) but it at least let’s crate developers implement the “business” logic only once.